grunt-banana-checker
Advanced tools
Comparing version 0.7.1 to 0.8.0
@@ -0,1 +1,13 @@ | ||
v0.8.0 / 2019-08-20 | ||
================== | ||
* Allow ignoring missing translations of blank source messages (Roan Kattouw) | ||
* Allow requiring each parameter to be used in translations (James D. Forrester) | ||
* Fix "languageMesages" typo (James D. Forrester) | ||
* Make "lacks documentation" message more specific (Thiemo Kreuz) | ||
* code: Commafy chained consts and fix directories (James D. Forrester) | ||
* build: Upgrade eslint-config-wikimedia to 0.13.1 (James D. Forrester) | ||
* build: Upgrade nyc from 13.3.0 to 14.1.1 (James D. Forrester) | ||
* build: Use template literals (James D. Forrester) | ||
v0.7.0 / 2019-01-08 | ||
@@ -2,0 +14,0 @@ ================== |
{ | ||
"name": "grunt-banana-checker", | ||
"version": "0.7.1", | ||
"version": "0.8.0", | ||
"description": "Checker for the 'Banana' JSON-file format for interface messages, as used by MediaWiki and jQuery.i18n.", | ||
@@ -31,5 +31,5 @@ "repository": { | ||
"devDependencies": { | ||
"eslint-config-wikimedia": "0.10.0", | ||
"nyc": "13.3.0", | ||
"grunt": "1.0.3" | ||
"eslint-config-wikimedia": "0.13.1", | ||
"nyc": "14.1.1", | ||
"grunt": "1.0.4" | ||
}, | ||
@@ -36,0 +36,0 @@ "scripts": { |
@@ -1,4 +0,4 @@ | ||
[![NPM version](https://badge.fury.io/js/grunt-banana-checker.svg)](http://badge.fury.io/js/grunt-banana-checker) [![Build Status](https://travis-ci.org/wikimedia/grunt-banana-checker.svg?branch=master)](https://travis-ci.org/wikimedia/grunt-banana-checker) | ||
[![NPM version](https://badge.fury.io/js/grunt-banana-checker.svg)](http://badge.fury.io/js/grunt-banana-checker) [![Build Status](https://travis-ci.org/wikimedia/banana-checker.svg?branch=master)](https://travis-ci.org/wikimedia/banana-checker) | ||
# grunt-banana-checker | ||
# banana-checker | ||
@@ -188,2 +188,8 @@ > Checker for the 'Banana' JSON-file format for interface messages, as used by MediaWiki and jQuery.i18n. | ||
#### requireCompletelyUsedParameters | ||
Type: `boolean` | ||
Default value: `false` | ||
Whether to fail if any translated message fails to use a parameter used in the primary message. | ||
#### requireCompleteTranslationLanguages | ||
@@ -202,1 +208,7 @@ Type: `string[]` | ||
Messages on which to fail if missing in any provided language. | ||
### ignoreMissingBlankTranslations | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to ignore missing translations whose original string is blank. |
@@ -26,2 +26,3 @@ 'use strict'; | ||
disallowUnusedTranslations: false, | ||
ignoreMissingBlankTranslations: true, | ||
@@ -59,3 +60,3 @@ requireCompleteMessageDocumentation: true, | ||
} catch ( e ) { | ||
logErr( 'Loading ' + type + ' messages failed: "' + e + '".' ); | ||
logErr( `Loading ${type} messages failed: "${e}".` ); | ||
ok = false; | ||
@@ -76,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; | ||
@@ -103,14 +104,17 @@ } | ||
var language = languageFile.match( jsonFilenameRegex )[ 1 ], | ||
languageMesages = messages( languageFile, language ), | ||
keys = keysNoMetadata( languageMesages, language ), | ||
languageMessages = messages( languageFile, language ), | ||
keys = keysNoMetadata( languageMessages, language ), | ||
blanks = [], | ||
duplicates = [], | ||
unuseds = [], | ||
missing = sourceMessageKeys.slice( 0 ); | ||
missing = sourceMessageKeys.slice( 0 ), | ||
unusedParameters = [], | ||
stack, originalParameters; | ||
for ( index in keys ) { | ||
message = keys[ index ]; | ||
originalParameters = sourceMessages[ message ].match( /\$\d/g ); | ||
if ( missing.indexOf( message ) !== -1 ) { | ||
if ( languageMesages[ message ] === sourceMessages[ message ] ) { | ||
if ( languageMessages[ message ] === sourceMessages[ message ] ) { | ||
duplicates.push( message ); | ||
@@ -123,6 +127,17 @@ } | ||
if ( typeof languageMesages[ message ] !== 'string' ) { | ||
if ( originalParameters ) { | ||
// eslint-disable-next-line no-loop-func | ||
stack = originalParameters.filter( function ( originalParameter ) { | ||
return languageMessages[ message ].indexOf( originalParameter ) === -1; | ||
} ); | ||
if ( stack.length ) { | ||
unusedParameters.push( { message, stack } ); | ||
} | ||
} | ||
if ( typeof languageMessages[ message ] !== 'string' ) { | ||
continue; | ||
} | ||
if ( languageMesages[ message ].trim() === '' ) { | ||
if ( languageMessages[ message ].trim() === '' ) { | ||
blanks.push( message ); | ||
@@ -132,4 +147,10 @@ } | ||
if ( options.ignoreMissingBlankTranslations ) { | ||
missing = missing.filter( function ( message ) { | ||
return sourceMessages[ message ] !== ''; | ||
} ); | ||
} | ||
translatedData[ language ] = { | ||
messages: languageMesages, | ||
messages: languageMessages, | ||
keys: keys, | ||
@@ -139,3 +160,4 @@ blank: blanks, | ||
unused: unuseds, | ||
missing: missing | ||
missing: missing, | ||
unusedParameters: unusedParameters | ||
}; | ||
@@ -192,8 +214,6 @@ } ); | ||
logErr( | ||
count + ' message' + ( count > 1 ? 's lack' : ' lacks' ) + ' documentation.' | ||
); | ||
logErr( `${count} message${( count > 1 ? 's lack' : ' lacks' )} documentation in qqq.json.` ); | ||
sourceMessageMissing.forEach( function ( message ) { | ||
logErr( 'Message "' + message + '" lacks documentation.' ); | ||
logErr( `Message "${message}" lacks documentation in qqq.json.` ); | ||
} ); | ||
@@ -208,8 +228,6 @@ } | ||
logErr( | ||
count + ' documented message' + ( count > 1 ? 's are' : ' is' ) + ' blank.' | ||
); | ||
logErr( `${count} documented message${( count > 1 ? 's are' : ' is' )} blank.` ); | ||
documentationMessageBlanks.forEach( function ( message ) { | ||
logErr( 'Message "' + message + '" is documented with a blank string.' ); | ||
logErr( `Message "${message}" is documented with a blank string.` ); | ||
} ); | ||
@@ -224,16 +242,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( function ( message ) { | ||
logErr( 'Message "' + message + '" should start with a lowercase character.' ); | ||
logErr( `Message "${message}" 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( function ( message ) { | ||
logErr( 'Message "' + message + '" should be in lowercase.' ); | ||
logErr( `Message "${message}" should be in lowercase.` ); | ||
} ); | ||
@@ -248,16 +262,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( function ( message ) { | ||
logErr( 'Message "' + message + '" should start with the required prefix "' + options.requireKeyPrefix[ 0 ] + '".' ); | ||
logErr( `Message "${message}" 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( function ( message ) { | ||
logErr( 'Message "' + message + '" should start with one of the required prefices.' ); | ||
logErr( `Message "${message}" should start with one of the required prefices.` ); | ||
} ); | ||
@@ -272,8 +282,6 @@ } | ||
logErr( | ||
count + ' documented message' + ( count > 1 ? 's are' : ' is' ) + ' undefined.' | ||
); | ||
logErr( `${count} documented message${( count > 1 ? 's are' : ' is' )} undefined.` ); | ||
documentationMessageKeys.forEach( function ( message ) { | ||
logErr( 'Message "' + message + '" is documented but undefined.' ); | ||
logErr( `Message "${message}" is documented but undefined.` ); | ||
} ); | ||
@@ -293,8 +301,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( function ( message ) { | ||
logErr( 'The translation of "' + message + '" is blank.' ); | ||
logErr( `The translation of "${message}" is blank.` ); | ||
} ); | ||
@@ -308,8 +313,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( function ( message ) { | ||
logErr( 'The translation of "' + message + '" duplicates the primary message.' ); | ||
logErr( `The translation of "${message}" duplicates the primary message.` ); | ||
} ); | ||
@@ -323,9 +325,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( function ( message ) { | ||
logErr( 'The translation of "' + message + '" is unused.' ); | ||
logErr( `The translation of "${message}" is unused.` ); | ||
} ); | ||
@@ -335,2 +333,22 @@ } | ||
if ( options.requireCompletelyUsedParameters ) { | ||
count = translatedData[ index ].unusedParameters.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
logErr( `The "${index}" translation has ${count} message${( count > 1 ? 's' : '' )} which fail${( count > 1 ? 's' : '' )} to use all parameters:` ); | ||
// eslint-disable-next-line no-loop-func | ||
translatedData[ index ].unusedParameters.forEach( function ( report ) { | ||
switch ( report.stack.length ) { | ||
case 1: | ||
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 ]}" .` ); | ||
break; | ||
default: | ||
logErr( `The translation of "${report.message}" fails to use the parameters "${report.stack.join( '", "' )}".` ); | ||
} | ||
} ); | ||
} | ||
} | ||
} | ||
@@ -351,6 +369,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( function ( message ) { | ||
logErr( 'The translation of "' + message + '" is missing.' ); | ||
logErr( `The translation of "${message}" is missing.` ); | ||
} ); | ||
@@ -369,8 +387,12 @@ } | ||
for ( message in translatedData[ index ].missing ) { | ||
// eslint-disable-next-line no-prototype-builtins | ||
if ( !translatedData[ index ].missing.hasOwnProperty( sourceMessageKeys[ message ] ) ) { | ||
if ( | ||
// eslint-disable-next-line no-prototype-builtins | ||
!translatedData[ index ].missing.hasOwnProperty( sourceMessageKeys[ message ] ) | ||
) { | ||
continue; | ||
} | ||
offset = options.requireCompleteTranslationMessages.indexOf( sourceMessageKeys[ message ] ); | ||
offset = options.requireCompleteTranslationMessages.indexOf( | ||
sourceMessageKeys[ message ] | ||
); | ||
@@ -385,6 +407,6 @@ if ( offset === -1 ) { | ||
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( function ( message ) { | ||
logErr( 'The required message "' + message + '" is missing.' ); | ||
logErr( `The required message "${message}" is missing.` ); | ||
} ); | ||
@@ -391,0 +413,0 @@ } |
@@ -28,2 +28,3 @@ #!/usr/bin/env node | ||
case 'disallowUnusedTranslations': | ||
case 'ignoreMissingBlankTranslations': | ||
case 'requireCompleteMessageDocumentation': | ||
@@ -69,2 +70,2 @@ case 'requireLowerCase': | ||
console.log( 'Checked ' + dirs.length + ' message ' + ( dirs.length > 1 ? 'directories' : 'directory' ) + '.' ); | ||
console.log( `Checked ${dirs.length} message director${( dirs.length > 1 ? 'ies' : 'y' )}.` ); |
@@ -25,4 +25,4 @@ const bananaChecker = require( '../src/banana.js' ); | ||
grunt.log.ok( messageDirs + ' message ' + ( messageDirs > 1 ? 'directories' : 'directory' ) + ' 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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
25268
419
213
0