Socket
Socket
Sign inDemoInstall

grunt-banana-checker

Package Overview
Dependencies
0
Maintainers
22
Versions
18
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.9.0 to 0.10.0

90

package.json
{
"name": "grunt-banana-checker",
"version": "0.9.0",
"description": "Checker for the 'Banana' JSON-file format for interface messages, as used by MediaWiki and jQuery.i18n.",
"repository": {
"type": "git",
"url": "git://github.com/wikimedia/grunt-banana-checker.git"
},
"homepage": "https://github.com/wikimedia/grunt-banana-checker",
"keywords": [
"gruntplugin",
"checker",
"banana"
],
"bin": {
"banana-checker": "src/cli.js"
},
"files": [
"src/",
"tasks/"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/wikimedia/grunt-banana-checker/issues"
},
"main": "src/banana.js",
"engines": {
"node": ">=8"
},
"devDependencies": {
"eslint-config-wikimedia": "0.15.1",
"nyc": "15.0.1",
"grunt": "1.1.0"
},
"scripts": {
"test": "nyc node test/test.js && eslint . && grunt banana && ./src/cli.js --requireLowerCase=0 test/simple/ test/requireLowerCase/full/"
},
"nyc": {
"exclude": [
"test"
],
"reporter": [
"text",
"html"
]
}
"name": "grunt-banana-checker",
"version": "0.10.0",
"description": "Checker for the 'Banana' JSON-file format for interface messages, as used by MediaWiki and jQuery.i18n.",
"repository": {
"type": "git",
"url": "https://github.com/wikimedia/banana-checker.git"
},
"homepage": "https://github.com/wikimedia/banana-checker",
"keywords": [
"gruntplugin",
"checker",
"banana"
],
"bin": {
"banana-checker": "src/cli.js"
},
"files": [
"src/",
"tasks/"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/wikimedia/banana-checker/issues"
},
"main": "src/banana.js",
"engines": {
"node": ">=8"
},
"devDependencies": {
"eslint-config-wikimedia": "0.22.1",
"nyc": "15.1.0",
"grunt": "1.5.3"
},
"scripts": {
"test": "nyc node test/test.js && eslint --cache . && grunt banana && ./src/cli.js --requireLowerCase=0 test/simple/ test/requireLowerCase/full/"
},
"nyc": {
"exclude": [
"test"
],
"reporter": [
"text",
"html"
]
}
}

@@ -213,1 +213,13 @@ [![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)

Whether to ignore missing translations whose original string is blank.
### allowLeadingWhitespace
Type: `boolean`
Default value: `true`
Whether to ignore leading whitespace in original or translated messages.
### allowTrailingWhitespace
Type: `boolean`
Default value: `true`
Whether to ignore trailing whitespace in original or translated messages.
'use strict';
var path = require( 'path' );
var fs = require( 'fs' );
const path = require( 'path' );
const fs = require( 'fs' );

@@ -16,4 +16,7 @@ /**

module.exports = function bananaChecker( dir, options, logErr ) {
var ok = true;
// Step 1: Read config and get set up.
let ok = true;
options = Object.assign( {

@@ -36,22 +39,16 @@ sourceFile: 'en.json',

allowLeadingWhitespace: true,
allowTrailingWhitespace: true,
skipIncompleteMessageDocumentation: []
}, options );
var message, index, offset,
// Source message data
sourceMessages, sourceMessageKeys,
// Documentation message data
documentationMessages, documentationMessageKeys,
// Translated message data
translatedFiles,
jsonFilenameRegex = /(.*)\.json$/,
translatedData = {},
documentationMessageBlanks = [],
sourceMessageMissing = [],
sourceMessageWrongCase = [],
sourceMessageWrongPrefix = [],
count = 0;
const jsonFilenameRegex = /(.*)\.json$/;
const leadingWhitespaceRegex = /^\s/;
const trailingWhitespaceRegex = /\s$/;
const translatedData = {};
function messages( filename, type ) {
var messageArray;
let messageArray;

@@ -70,8 +67,7 @@ try {

function keysNoMetadata( messageArray, type ) {
var keys, offset;
const keys = Object.keys( messageArray );
keys = Object.keys( messageArray );
const keyOffset = keys.indexOf( '@metadata' );
offset = keys.indexOf( '@metadata' );
if ( offset === -1 ) {
if ( keyOffset === -1 ) {
if ( options.requireMetadata ) {

@@ -82,3 +78,3 @@ logErr( `No metadata block in the ${type} messages file.` );

} else {
keys.splice( offset, 1 );
keys.splice( keyOffset, 1 );
}

@@ -89,29 +85,36 @@

sourceMessages = messages( options.sourceFile, 'source' );
sourceMessageKeys = keysNoMetadata( sourceMessages, 'source' );
// Source message data
const sourceMessages = messages( options.sourceFile, 'source' );
const sourceMessageKeys = keysNoMetadata( sourceMessages, 'source' );
documentationMessages = messages( options.documentationFile, 'documentation' );
documentationMessageKeys = keysNoMetadata( documentationMessages, 'documentation' );
// Documentation message data
const documentationMessages = messages( options.documentationFile, 'documentation' );
const documentationMessageKeys = keysNoMetadata( documentationMessages, 'documentation' );
translatedFiles = fs.readdirSync( dir ).filter( function ( value ) {
return (
value !== options.sourceFile &&
value !== options.documentationFile &&
value.match( jsonFilenameRegex )
);
} );
// Translated message data
const translatedFiles = fs.readdirSync( dir ).filter( ( value ) =>
value !== options.sourceFile &&
value !== options.documentationFile &&
jsonFilenameRegex.test( value )
);
translatedFiles.forEach( function ( languageFile ) {
var language = languageFile.match( jsonFilenameRegex )[ 1 ],
languageMessages = messages( languageFile, language ),
keys = keysNoMetadata( languageMessages, language ),
blanks = [],
duplicates = [],
unuseds = [],
missing = sourceMessageKeys.slice( 0 ),
unusedParameters = [],
stack, originalParameters;
// Step 2: Walk through files and check for failures.
for ( index in keys ) {
message = keys[ index ];
translatedFiles.forEach( ( languageFile ) => {
const language = languageFile.match( jsonFilenameRegex )[ 1 ];
const languageMessages = messages( languageFile, language );
const keys = keysNoMetadata( languageMessages, language );
const blanks = [];
const duplicates = [];
const unuseds = [];
const unusedParameters = [];
let leadingWhitespace = [];
let trailingWhitespace = [];
let missing = sourceMessageKeys.slice( 0 );
let stack, originalParameters;
for ( const index in keys ) {
const message = keys[ index ];
if ( sourceMessages[ message ] === undefined ) {

@@ -126,3 +129,3 @@ // An unused translation. This happens on commits that remove messages,

if ( missing.indexOf( message ) !== -1 ) {
if ( missing.includes( message ) ) {
if ( languageMessages[ message ] === sourceMessages[ message ] ) {

@@ -137,6 +140,5 @@ duplicates.push( message );

if ( originalParameters ) {
// eslint-disable-next-line no-loop-func
stack = originalParameters.filter( function ( originalParameter ) {
return languageMessages[ message ].indexOf( originalParameter ) === -1;
} );
stack = originalParameters.filter( ( originalParameter ) =>
!languageMessages[ message ].includes( originalParameter )
);

@@ -156,6 +158,18 @@ if ( stack.length ) {

if ( !options.allowLeadingWhitespace ) {
leadingWhitespace = keys.filter( ( message ) =>
leadingWhitespaceRegex.test( sourceMessages[ message ] )
);
}
if ( !options.allowTrailingWhitespace ) {
trailingWhitespace = keys.filter( ( message ) =>
trailingWhitespaceRegex.test( sourceMessages[ message ] )
);
}
if ( options.ignoreMissingBlankTranslations ) {
missing = missing.filter( function ( message ) {
return sourceMessages[ message ] !== '';
} );
missing = missing.filter( ( messageName ) =>
sourceMessages[ messageName ] !== ''
);
}

@@ -170,16 +184,20 @@

missing: missing,
unusedParameters: unusedParameters
unusedParameters: unusedParameters,
leadingWhitespace: leadingWhitespace,
trailingWhitespace: trailingWhitespace
};
} );
let sourceMessageWrongCase = [];
if ( options.requireLowerCase === 'initial' ) {
sourceMessageWrongCase = sourceMessageKeys.filter( function ( value ) {
return ( value !== '' && value[ 0 ] !== value[ 0 ].toLowerCase() );
} );
sourceMessageWrongCase = sourceMessageKeys.filter( ( value ) =>
( value !== '' && value[ 0 ] !== value[ 0 ].toLowerCase() )
);
} else if ( options.requireLowerCase ) {
sourceMessageWrongCase = sourceMessageKeys.filter( function ( value ) {
return value !== value.toLowerCase();
} );
sourceMessageWrongCase = sourceMessageKeys.filter( ( value ) =>
value !== value.toLowerCase()
);
}
let sourceMessageWrongPrefix = [];
if ( options.requireKeyPrefix.length ) {

@@ -189,13 +207,39 @@ if ( typeof options.requireKeyPrefix === 'string' ) {

}
sourceMessageWrongPrefix = sourceMessageKeys.filter( function ( key ) {
return !options.requireKeyPrefix.some( function ( prefix ) {
return key.startsWith( prefix );
} );
} );
sourceMessageWrongPrefix = sourceMessageKeys.filter( ( key ) =>
!options.requireKeyPrefix.some( ( prefix ) =>
key.startsWith( prefix )
)
);
}
let sourceMessageLeadingWhitespace = [];
let documentationMessageLeadingWhitespace = [];
if ( !options.allowLeadingWhitespace ) {
sourceMessageLeadingWhitespace = sourceMessageKeys.filter(
( message ) => leadingWhitespaceRegex.test( sourceMessages[ message ] )
);
documentationMessageLeadingWhitespace = documentationMessageKeys.filter(
( message ) => leadingWhitespaceRegex.test( documentationMessages[ message ] )
);
}
let sourceMessageTrailingWhitespace = [];
let documentationMessageTrailingWhitespace = [];
if ( !options.allowTrailingWhitespace ) {
sourceMessageTrailingWhitespace = sourceMessageKeys.filter(
( message ) => trailingWhitespaceRegex.test( sourceMessages[ message ] )
);
documentationMessageTrailingWhitespace = documentationMessageKeys.filter(
( message ) => trailingWhitespaceRegex.test( documentationMessages[ message ] )
);
}
let sourceMessageMissing = [];
const documentationMessageBlanks = [];
while ( sourceMessageKeys.length > 0 ) {
message = sourceMessageKeys[ 0 ];
const message = sourceMessageKeys[ 0 ];
offset = documentationMessageKeys.indexOf( message );
const offset = documentationMessageKeys.indexOf( message );

@@ -215,7 +259,10 @@ if ( offset !== -1 ) {

// Step 3: Go through failures and report them, based on config.
let count = 0;
if ( options.requireCompleteMessageDocumentation ) {
// Filter out any missing message that is OK to be skipped
sourceMessageMissing = sourceMessageMissing.filter( function ( value ) {
return options.skipIncompleteMessageDocumentation.indexOf( value ) === -1;
} );
sourceMessageMissing = sourceMessageMissing.filter( ( value ) =>
!options.skipIncompleteMessageDocumentation.includes( value )
);
count = sourceMessageMissing.length;

@@ -227,4 +274,4 @@ if ( count > 0 ) {

sourceMessageMissing.forEach( function ( message ) {
logErr( `Message "${message}" lacks documentation in qqq.json.` );
sourceMessageMissing.forEach( ( messageName ) => {
logErr( `Message "${messageName}" lacks documentation in qqq.json.` );
} );

@@ -241,4 +288,4 @@ }

documentationMessageBlanks.forEach( function ( message ) {
logErr( `Message "${message}" is documented with a blank string.` );
documentationMessageBlanks.forEach( ( messageName ) => {
logErr( `Message "${messageName}" is documented with a blank string.` );
} );

@@ -248,37 +295,41 @@ }

count = sourceMessageWrongCase.length;
if ( count > 0 ) {
ok = false;
if ( options.requireLowerCase ) {
count = sourceMessageWrongCase.length;
if ( count > 0 ) {
ok = false;
if ( options.requireLowerCase === 'initial' ) {
logErr( `${count} message${( count > 1 ? 's do' : ' does' )} not start with a lowercase character.` );
if ( options.requireLowerCase === 'initial' ) {
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.` );
} );
} else {
logErr( `${count} message${( count > 1 ? 's are' : ' is' )} not wholly lowercase.` );
sourceMessageWrongCase.forEach( ( messageName ) => {
logErr( `Message "${messageName}" should start with a lowercase character.` );
} );
} else {
logErr( `${count} message${( count > 1 ? 's are' : ' is' )} not wholly lowercase.` );
sourceMessageWrongCase.forEach( function ( message ) {
logErr( `Message "${message}" should be in lowercase.` );
} );
sourceMessageWrongCase.forEach( ( messageName ) => {
logErr( `Message "${messageName}" should be in lowercase.` );
} );
}
}
}
count = sourceMessageWrongPrefix.length;
if ( count > 0 ) {
ok = false;
if ( options.requireKeyPrefix.length ) {
count = sourceMessageWrongPrefix.length;
if ( count > 0 ) {
ok = false;
if ( options.requireKeyPrefix.length === 1 ) {
logErr( `${count} message${( count > 1 ? 's do' : ' does' )} not start with the required prefix "${options.requireKeyPrefix[ 0 ]}".` );
if ( options.requireKeyPrefix.length === 1 ) {
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 ]}".` );
} );
} else {
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 the required prefix "${options.requireKeyPrefix[ 0 ]}".` );
} );
} else {
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.` );
} );
sourceMessageWrongPrefix.forEach( ( messageName ) => {
logErr( `Message "${messageName}" should start with one of the required prefices.` );
} );
}
}

@@ -294,4 +345,4 @@ }

documentationMessageKeys.forEach( function ( message ) {
logErr( `Message "${message}" is documented but undefined.` );
documentationMessageKeys.forEach( ( messageName ) => {
logErr( `Message "${messageName}" is documented but undefined.` );
} );

@@ -301,3 +352,43 @@ }

for ( index in translatedData ) {
if ( !options.allowLeadingWhitespace ) {
count = sourceMessageLeadingWhitespace.length;
if ( count > 0 ) {
ok = false;
logErr( `${count} message${( count > 1 ? 's have' : ' has' )} leading whitespace:` );
sourceMessageLeadingWhitespace.forEach( ( message ) => {
logErr( `Message "${message}" has leading whitespace` );
} );
}
count = documentationMessageLeadingWhitespace.length;
if ( count > 0 ) {
ok = false;
logErr( `${count} message documentation${( count > 1 ? 's have' : ' has' )} leading whitespace:` );
documentationMessageLeadingWhitespace.forEach( ( message ) => {
logErr( `Message documentation "${message}" has leading whitespace` );
} );
}
}
if ( !options.allowTrailingWhitespace ) {
count = sourceMessageTrailingWhitespace.length;
if ( count > 0 ) {
ok = false;
logErr( `${count} message${( count > 1 ? 's have' : ' has' )} trailing whitespace:` );
sourceMessageTrailingWhitespace.forEach( ( message ) => {
logErr( `Message "${message}" has trailing whitespace` );
} );
}
count = documentationMessageTrailingWhitespace.length;
if ( count > 0 ) {
ok = false;
logErr( `${count} message documentation${( count > 1 ? 's have' : ' has' )} trailing whitespace:` );
documentationMessageTrailingWhitespace.forEach( ( message ) => {
logErr( `Message documentation "${message}" has trailing whitespace` );
} );
}
}
for ( const index in translatedData ) {
// eslint-disable-next-line no-prototype-builtins

@@ -313,4 +404,4 @@ if ( !translatedData.hasOwnProperty( index ) ) {

logErr( `The "${index}" translation has ${count} blank translation${( count > 1 ? 's' : '' )}:` );
translatedData[ index ].blank.forEach( function ( message ) {
logErr( `The translation of "${message}" is blank.` );
translatedData[ index ].blank.forEach( ( messageName ) => {
logErr( `The translation of "${messageName}" is blank.` );
} );

@@ -325,4 +416,4 @@ }

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.` );
translatedData[ index ].duplicate.forEach( ( messageName ) => {
logErr( `The translation of "${messageName}" duplicates the primary message.` );
} );

@@ -337,4 +428,4 @@ }

logErr( `The "${index}" translation has ${count} unused translation${( count > 1 ? 's' : '' )}:` );
translatedData[ index ].unused.forEach( function ( message ) {
logErr( `The translation of "${message}" is unused.` );
translatedData[ index ].unused.forEach( ( messageName ) => {
logErr( `The translation of "${messageName}" is unused.` );
} );

@@ -349,4 +440,4 @@ }

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 ) {
translatedData[ index ].unusedParameters.forEach( ( report ) => {
switch ( report.stack.length ) {

@@ -365,10 +456,32 @@ case 1:

}
if ( !options.allowLeadingWhitespace ) {
count = translatedData[ index ].leadingWhitespace.length;
if ( count > 0 ) {
ok = false;
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.` );
} );
}
}
if ( !options.allowTrailingWhitespace ) {
count = translatedData[ index ].trailingWhitespace.length;
if ( count > 0 ) {
ok = false;
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.` );
} );
}
}
}
if ( options.requireCompleteTranslationLanguages.length ) {
for ( index in translatedData ) {
for ( const index in translatedData ) {
if (
// eslint-disable-next-line no-prototype-builtins
!translatedData.hasOwnProperty( index ) ||
( options.requireCompleteTranslationLanguages.indexOf( index ) === -1 )
( !options.requireCompleteTranslationLanguages.includes( index ) )
) {

@@ -383,4 +496,4 @@ continue;

translatedData[ index ].missing.forEach( function ( message ) {
logErr( `The translation of "${message}" is missing.` );
translatedData[ index ].missing.forEach( ( messageName ) => {
logErr( `The translation of "${messageName}" is missing.` );
} );

@@ -392,3 +505,3 @@ }

if ( options.requireCompleteTranslationMessages.length ) {
for ( index in translatedData ) {
for ( const index in translatedData ) {
// eslint-disable-next-line no-prototype-builtins

@@ -399,3 +512,3 @@ if ( !translatedData.hasOwnProperty( index ) ) {

for ( message in translatedData[ index ].missing ) {
for ( const message in translatedData[ index ].missing ) {
if (

@@ -408,3 +521,3 @@ // eslint-disable-next-line no-prototype-builtins

offset = options.requireCompleteTranslationMessages.indexOf(
const offset = options.requireCompleteTranslationMessages.indexOf(
sourceMessageKeys[ message ]

@@ -423,4 +536,4 @@ );

translatedData[ index ].missing.forEach( function ( message ) {
logErr( `The required message "${message}" is missing.` );
translatedData[ index ].missing.forEach( ( messageName ) => {
logErr( `The required message "${messageName}" is missing.` );
} );

@@ -427,0 +540,0 @@ }

@@ -26,2 +26,4 @@ #!/usr/bin/env node

case 'disallowUnusedTranslations':
case 'allowLeadingWhitespace':
case 'allowTrailingWhitespace':
case 'ignoreMissingBlankTranslations':

@@ -53,2 +55,3 @@ case 'requireCompleteMessageDocumentation':

console.error( 'banana-check: Specify one or more directories.' );
// eslint-disable-next-line no-process-exit
process.exit( 1 );

@@ -66,2 +69,3 @@ }

if ( !result ) {
// eslint-disable-next-line no-process-exit
process.exit( 1 );

@@ -68,0 +72,0 @@ }

@@ -0,1 +1,3 @@

'use strict';
const bananaChecker = require( '../src/banana.js' );

@@ -2,0 +4,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc