grunt-banana-checker
Advanced tools
Comparing version 0.11.1 to 0.12.0
{ | ||
"name": "grunt-banana-checker", | ||
"version": "0.11.1", | ||
"version": "0.12.0", | ||
"description": "Checker for the 'Banana' JSON-file format for interface messages, as used by MediaWiki and jQuery.i18n.", | ||
@@ -28,8 +28,8 @@ "repository": { | ||
"engines": { | ||
"node": ">=8" | ||
"node": ">=16" | ||
}, | ||
"devDependencies": { | ||
"eslint-config-wikimedia": "0.25.0", | ||
"nyc": "15.1.0", | ||
"grunt": "1.6.1" | ||
"eslint-config-wikimedia": "0.27.0", | ||
"grunt": "1.6.1", | ||
"nyc": "15.1.0" | ||
}, | ||
@@ -36,0 +36,0 @@ "scripts": { |
@@ -118,2 +118,11 @@ # banana-checker | ||
#### autofix | ||
Type: `boolean` | ||
Default value: `false` | ||
If enabled, try to automatically fix issues detected by some checks. | ||
Note that autofixing will not affect the check result - the checker will fail | ||
even if all errors were automatically fixed. | ||
#### sourceFile | ||
@@ -120,0 +129,0 @@ Type: `string` |
@@ -42,8 +42,10 @@ 'use strict'; | ||
skipIncompleteMessageDocumentation: [] | ||
skipIncompleteMessageDocumentation: [], | ||
autofix: false | ||
}, options ); | ||
const jsonFilenameRegex = /(.*)\.json$/; | ||
const leadingWhitespaceRegex = /^\s/; | ||
const trailingWhitespaceRegex = /\s$/; | ||
const leadingWhitespaceRegex = /^\s+/; | ||
const trailingWhitespaceRegex = /\s+$/; | ||
@@ -59,3 +61,3 @@ const translatedData = {}; | ||
} catch ( e ) { | ||
logErr( `Loading ${type} messages failed: "${e}".` ); | ||
logErr( `Loading ${ type } messages failed: "${ e }".` ); | ||
ok = false; | ||
@@ -75,3 +77,3 @@ throw e; | ||
if ( options.requireMetadata ) { | ||
logErr( `No metadata block in the ${type} messages file.` ); | ||
logErr( `No metadata block in the ${ type } messages file.` ); | ||
ok = false; | ||
@@ -86,5 +88,14 @@ } | ||
function writeFile( filename, messageData ) { | ||
// eslint-disable-next-line security/detect-non-literal-fs-filename | ||
fs.writeFileSync( | ||
path.resolve( dir, filename ), | ||
JSON.stringify( messageData, null, '\t' ) + '\n' | ||
); | ||
} | ||
// Source message data | ||
const sourceMessages = messages( options.sourceFile, 'source' ); | ||
const sourceMessageKeys = keysNoMetadata( sourceMessages, 'source' ); | ||
let sourceMessagesModified = false; | ||
@@ -94,2 +105,3 @@ // Documentation message data | ||
const documentationMessageKeys = keysNoMetadata( documentationMessages, 'documentation' ); | ||
let documentationMessagesModified = false; | ||
@@ -120,2 +132,3 @@ // Translated message data | ||
let stack, originalParameters; | ||
let modified = false; | ||
@@ -162,4 +175,11 @@ for ( const index in keys ) { | ||
leadingWhitespace = keys.filter( ( message ) => | ||
leadingWhitespaceRegex.test( sourceMessages[ message ] ) | ||
leadingWhitespaceRegex.test( languageMessages[ message ] ) | ||
); | ||
if ( leadingWhitespace.length > 0 && options.autofix ) { | ||
modified = true; | ||
leadingWhitespace.forEach( ( message ) => { | ||
languageMessages[ message ] = languageMessages[ message ].replace( leadingWhitespaceRegex, '' ); | ||
} ); | ||
} | ||
} | ||
@@ -169,4 +189,11 @@ | ||
trailingWhitespace = keys.filter( ( message ) => | ||
trailingWhitespaceRegex.test( sourceMessages[ message ] ) | ||
trailingWhitespaceRegex.test( languageMessages[ message ] ) | ||
); | ||
if ( trailingWhitespace.length > 0 && options.autofix ) { | ||
modified = true; | ||
trailingWhitespace.forEach( ( message ) => { | ||
languageMessages[ message ] = languageMessages[ message ].replace( trailingWhitespaceRegex, '' ); | ||
} ); | ||
} | ||
} | ||
@@ -176,6 +203,10 @@ | ||
missing = missing.filter( ( messageName ) => | ||
sourceMessages[ messageName ] !== '' | ||
languageMessages[ messageName ] !== '' | ||
); | ||
} | ||
if ( modified ) { | ||
writeFile( languageFile, languageMessages ); | ||
} | ||
translatedData[ language ] = { | ||
@@ -223,2 +254,12 @@ messages: languageMessages, | ||
); | ||
if ( sourceMessageLeadingWhitespace.length > 0 && options.autofix ) { | ||
sourceMessagesModified = true; | ||
// This is modifying the sourceMessages object - from this point onward | ||
// it's not always the exact same thing that was loaded from the source. | ||
// This should be fine - we do want to run the lints on the messages we're | ||
// writing to the file anyway. | ||
sourceMessageLeadingWhitespace.forEach( ( message ) => { | ||
sourceMessages[ message ] = sourceMessages[ message ].replace( leadingWhitespaceRegex, '' ); | ||
} ); | ||
} | ||
@@ -228,2 +269,9 @@ documentationMessageLeadingWhitespace = documentationMessageKeys.filter( | ||
); | ||
if ( documentationMessageLeadingWhitespace.length > 0 && options.autofix ) { | ||
documentationMessagesModified = true; | ||
// We modify things here too - see the comment above. | ||
documentationMessageLeadingWhitespace.forEach( ( message ) => { | ||
documentationMessages[ message ] = documentationMessages[ message ].replace( leadingWhitespaceRegex, '' ); | ||
} ); | ||
} | ||
} | ||
@@ -237,2 +285,8 @@ | ||
); | ||
if ( sourceMessageTrailingWhitespace.length > 0 && options.autofix ) { | ||
sourceMessagesModified = true; | ||
sourceMessageTrailingWhitespace.forEach( ( message ) => { | ||
sourceMessages[ message ] = sourceMessages[ message ].replace( trailingWhitespaceRegex, '' ); | ||
} ); | ||
} | ||
@@ -242,2 +296,8 @@ documentationMessageTrailingWhitespace = documentationMessageKeys.filter( | ||
); | ||
if ( documentationMessageTrailingWhitespace.length > 0 && options.autofix ) { | ||
documentationMessagesModified = true; | ||
documentationMessageTrailingWhitespace.forEach( ( message ) => { | ||
documentationMessages[ message ] = documentationMessages[ message ].replace( trailingWhitespaceRegex, '' ); | ||
} ); | ||
} | ||
} | ||
@@ -265,2 +325,9 @@ | ||
if ( sourceMessagesModified ) { | ||
writeFile( options.sourceFile, sourceMessages ); | ||
} | ||
if ( documentationMessagesModified ) { | ||
writeFile( options.documentationFile, documentationMessages ); | ||
} | ||
// Step 3: Go through failures and report them, based on config. | ||
@@ -278,6 +345,6 @@ | ||
logErr( `${count} message${( count > 1 ? 's lack' : ' lacks' )} documentation in qqq.json.` ); | ||
logErr( `${ count } message${ ( count > 1 ? 's lack' : ' lacks' ) } documentation in qqq.json.` ); | ||
sourceMessageMissing.forEach( ( messageName ) => { | ||
logErr( `Message "${messageName}" lacks documentation in qqq.json.` ); | ||
logErr( `Message "${ messageName }" lacks documentation in qqq.json.` ); | ||
} ); | ||
@@ -292,6 +359,6 @@ } | ||
logErr( `${count} documented message${( count > 1 ? 's are' : ' is' )} blank.` ); | ||
logErr( `${ count } documented message${ ( count > 1 ? 's are' : ' is' ) } blank.` ); | ||
documentationMessageBlanks.forEach( ( messageName ) => { | ||
logErr( `Message "${messageName}" is documented with a blank string.` ); | ||
logErr( `Message "${ messageName }" is documented with a blank string.` ); | ||
} ); | ||
@@ -307,12 +374,12 @@ } | ||
if ( options.requireLowerCase === 'initial' ) { | ||
logErr( `${count} message${( count > 1 ? 's do' : ' does' )} not start with a lowercase character.` ); | ||
logErr( `${ count } message${ ( count > 1 ? 's do' : ' does' ) } not start with a lowercase character.` ); | ||
sourceMessageWrongCase.forEach( ( messageName ) => { | ||
logErr( `Message "${messageName}" should start with a lowercase character.` ); | ||
logErr( `Message "${ messageName }" should start with a lowercase character.` ); | ||
} ); | ||
} else { | ||
logErr( `${count} message${( count > 1 ? 's are' : ' is' )} not wholly lowercase.` ); | ||
logErr( `${ count } message${ ( count > 1 ? 's are' : ' is' ) } not wholly lowercase.` ); | ||
sourceMessageWrongCase.forEach( ( messageName ) => { | ||
logErr( `Message "${messageName}" should be in lowercase.` ); | ||
logErr( `Message "${ messageName }" should be in lowercase.` ); | ||
} ); | ||
@@ -329,12 +396,12 @@ } | ||
if ( options.requireKeyPrefix.length === 1 ) { | ||
logErr( `${count} message${( count > 1 ? 's do' : ' does' )} not start with the required prefix "${options.requireKeyPrefix[ 0 ]}".` ); | ||
logErr( `${ count } message${ ( count > 1 ? 's do' : ' does' ) } not start with the required prefix "${ options.requireKeyPrefix[ 0 ] }".` ); | ||
sourceMessageWrongPrefix.forEach( ( messageName ) => { | ||
logErr( `Message "${messageName}" should start with the required prefix "${options.requireKeyPrefix[ 0 ]}".` ); | ||
logErr( `Message "${ messageName }" should start with the required prefix "${ options.requireKeyPrefix[ 0 ] }".` ); | ||
} ); | ||
} else { | ||
logErr( `${count} message${( count > 1 ? 's do' : ' does' )} not start with any of the required prefices.'` ); | ||
logErr( `${ count } message${ ( count > 1 ? 's do' : ' does' ) } not start with any of the required prefices.'` ); | ||
sourceMessageWrongPrefix.forEach( ( messageName ) => { | ||
logErr( `Message "${messageName}" should start with one of the required prefices.` ); | ||
logErr( `Message "${ messageName }" should start with one of the required prefices.` ); | ||
} ); | ||
@@ -350,6 +417,6 @@ } | ||
logErr( `${count} documented message${( count > 1 ? 's are' : ' is' )} undefined.` ); | ||
logErr( `${ count } documented message${ ( count > 1 ? 's are' : ' is' ) } undefined.` ); | ||
documentationMessageKeys.forEach( ( messageName ) => { | ||
logErr( `Message "${messageName}" is documented but undefined.` ); | ||
logErr( `Message "${ messageName }" is documented but undefined.` ); | ||
} ); | ||
@@ -363,5 +430,5 @@ } | ||
ok = false; | ||
logErr( `${count} message${( count > 1 ? 's have' : ' has' )} leading whitespace:` ); | ||
logErr( `${ count } message${ ( count > 1 ? 's have' : ' has' ) } leading whitespace:` ); | ||
sourceMessageLeadingWhitespace.forEach( ( message ) => { | ||
logErr( `Message "${message}" has leading whitespace` ); | ||
logErr( `Message "${ message }" has leading whitespace` ); | ||
} ); | ||
@@ -373,5 +440,5 @@ } | ||
ok = false; | ||
logErr( `${count} message documentation${( count > 1 ? 's have' : ' has' )} leading whitespace:` ); | ||
logErr( `${ count } message documentation${ ( count > 1 ? 's have' : ' has' ) } leading whitespace:` ); | ||
documentationMessageLeadingWhitespace.forEach( ( message ) => { | ||
logErr( `Message documentation "${message}" has leading whitespace` ); | ||
logErr( `Message documentation "${ message }" has leading whitespace` ); | ||
} ); | ||
@@ -385,5 +452,5 @@ } | ||
ok = false; | ||
logErr( `${count} message${( count > 1 ? 's have' : ' has' )} trailing whitespace:` ); | ||
logErr( `${ count } message${ ( count > 1 ? 's have' : ' has' ) } trailing whitespace:` ); | ||
sourceMessageTrailingWhitespace.forEach( ( message ) => { | ||
logErr( `Message "${message}" has trailing whitespace` ); | ||
logErr( `Message "${ message }" has trailing whitespace` ); | ||
} ); | ||
@@ -395,5 +462,5 @@ } | ||
ok = false; | ||
logErr( `${count} message documentation${( count > 1 ? 's have' : ' has' )} trailing whitespace:` ); | ||
logErr( `${ count } message documentation${ ( count > 1 ? 's have' : ' has' ) } trailing whitespace:` ); | ||
documentationMessageTrailingWhitespace.forEach( ( message ) => { | ||
logErr( `Message documentation "${message}" has trailing whitespace` ); | ||
logErr( `Message documentation "${ message }" has trailing whitespace` ); | ||
} ); | ||
@@ -413,5 +480,5 @@ } | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} blank translation${( count > 1 ? 's' : '' )}:` ); | ||
logErr( `The "${ index }" translation has ${ count } blank translation${ ( count > 1 ? 's' : '' ) }:` ); | ||
translatedData[ index ].blank.forEach( ( messageName ) => { | ||
logErr( `The translation of "${messageName}" is blank.` ); | ||
logErr( `The translation of "${ messageName }" is blank.` ); | ||
} ); | ||
@@ -425,5 +492,5 @@ } | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} duplicate translation${( count > 1 ? 's' : '' )}:` ); | ||
logErr( `The "${ index }" translation has ${ count } duplicate translation${ ( count > 1 ? 's' : '' ) }:` ); | ||
translatedData[ index ].duplicate.forEach( ( messageName ) => { | ||
logErr( `The translation of "${messageName}" duplicates the primary message.` ); | ||
logErr( `The translation of "${ messageName }" duplicates the primary message.` ); | ||
} ); | ||
@@ -437,5 +504,5 @@ } | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} unused translation${( count > 1 ? 's' : '' )}:` ); | ||
logErr( `The "${ index }" translation has ${ count } unused translation${ ( count > 1 ? 's' : '' ) }:` ); | ||
translatedData[ index ].unused.forEach( ( messageName ) => { | ||
logErr( `The translation of "${messageName}" is unused.` ); | ||
logErr( `The translation of "${ messageName }" is unused.` ); | ||
} ); | ||
@@ -449,3 +516,3 @@ } | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} message${( count > 1 ? 's' : '' )} which fail${( count > 1 ? 's' : '' )} to use all parameters:` ); | ||
logErr( `The "${ index }" translation has ${ count } message${ ( count > 1 ? 's' : '' ) } which fail${ ( count > 1 ? 's' : '' ) } to use all parameters:` ); | ||
@@ -455,9 +522,9 @@ translatedData[ index ].unusedParameters.forEach( ( report ) => { | ||
case 1: | ||
logErr( `The translation of "${report.message}" fails to use the parameter "${report.stack[ 0 ]}".` ); | ||
logErr( `The translation of "${ report.message }" fails to use the parameter "${ report.stack[ 0 ] }".` ); | ||
break; | ||
case 2: | ||
logErr( `The translation of "${report.message}" fails to use the parameters "${report.stack[ 0 ]}" and "${report.stack[ 1 ]}" .` ); | ||
logErr( `The translation of "${ report.message }" fails to use the parameters "${ report.stack[ 0 ] }" and "${ report.stack[ 1 ] }" .` ); | ||
break; | ||
default: | ||
logErr( `The translation of "${report.message}" fails to use the parameters "${report.stack.join( '", "' )}".` ); | ||
logErr( `The translation of "${ report.message }" fails to use the parameters "${ report.stack.join( '", "' ) }".` ); | ||
} | ||
@@ -472,5 +539,5 @@ } ); | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} translation${( count > 1 ? 's' : '' )} with leading whitespace:` ); | ||
logErr( `The "${ index }" translation has ${ count } translation${ ( count > 1 ? 's' : '' ) } with leading whitespace:` ); | ||
translatedData[ index ].leadingWhitespace.forEach( ( message ) => { | ||
logErr( `The translation of "${message}" has leading whitespace.` ); | ||
logErr( `The translation of "${ message }" has leading whitespace.` ); | ||
} ); | ||
@@ -484,5 +551,5 @@ } | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} translation${( count > 1 ? 's' : '' )} with trailing whitespace:` ); | ||
logErr( `The "${ index }" translation has ${ count } translation${ ( count > 1 ? 's' : '' ) } with trailing whitespace:` ); | ||
translatedData[ index ].trailingWhitespace.forEach( ( message ) => { | ||
logErr( `The translation of "${message}" has trailing whitespace.` ); | ||
logErr( `The translation of "${ message }" has trailing whitespace.` ); | ||
} ); | ||
@@ -506,6 +573,6 @@ } | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} missing translation${( count > 1 ? 's' : '' )}:` ); | ||
logErr( `The "${ index }" translation has ${ count } missing translation${ ( count > 1 ? 's' : '' ) }:` ); | ||
translatedData[ index ].missing.forEach( ( messageName ) => { | ||
logErr( `The translation of "${messageName}" is missing.` ); | ||
logErr( `The translation of "${ messageName }" is missing.` ); | ||
} ); | ||
@@ -543,6 +610,6 @@ } | ||
ok = false; | ||
logErr( `The "${index}" translation is missing ${count} required message{( count > 1 ? 's' : '' )}:` ); | ||
logErr( `The "${ index }" translation is missing ${ count } required message{( count > 1 ? 's' : '' )}:` ); | ||
translatedData[ index ].missing.forEach( ( messageName ) => { | ||
logErr( `The required message "${messageName}" is missing.` ); | ||
logErr( `The required message "${ messageName }" is missing.` ); | ||
} ); | ||
@@ -549,0 +616,0 @@ } |
@@ -23,2 +23,3 @@ #!/usr/bin/env node | ||
// Boolean options | ||
case 'autofix': | ||
case 'disallowBlankTranslations': | ||
@@ -41,3 +42,3 @@ case 'disallowUnusedDocumentation': | ||
} else { | ||
console.error( `banana-check: Invalid option ignored, --${key}=${value}` ); | ||
console.error( `banana-check: Invalid option ignored, --${ key }=${ value }` ); | ||
} | ||
@@ -54,3 +55,3 @@ break; | ||
default: | ||
console.error( `banana-check: Invalid option ignored, --${key}` ); | ||
console.error( `banana-check: Invalid option ignored, --${ key }` ); | ||
} | ||
@@ -61,3 +62,3 @@ } | ||
console.error( 'banana-check: Specify one or more directories.' ); | ||
// eslint-disable-next-line no-process-exit | ||
// eslint-disable-next-line n/no-process-exit | ||
process.exit( 1 ); | ||
@@ -67,14 +68,15 @@ } | ||
const bananaChecker = require( '../src/banana.js' ); | ||
const result = dirs.every( ( dir ) => { | ||
return bananaChecker( | ||
let result = true; | ||
dirs.forEach( ( dir ) => { | ||
result = bananaChecker( | ||
dir, | ||
options, | ||
console.error | ||
); | ||
) && result; | ||
} ); | ||
if ( !result ) { | ||
// eslint-disable-next-line no-process-exit | ||
// eslint-disable-next-line n/no-process-exit | ||
process.exit( 1 ); | ||
} | ||
console.log( `Checked ${dirs.length} message director${( dirs.length > 1 ? 'ies' : 'y' )}.` ); | ||
console.log( `Checked ${ dirs.length } message director${ ( dirs.length > 1 ? 'ies' : 'y' ) }.` ); |
@@ -27,4 +27,4 @@ 'use strict'; | ||
grunt.log.ok( `${messageDirs} message director${( messageDirs > 1 ? 'ies' : 'y' )} checked.` ); | ||
grunt.log.ok( `${ messageDirs } message director${ ( messageDirs > 1 ? 'ies' : 'y' ) } checked.` ); | ||
} ); | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
29864
591
232