grunt-banana-checker
Advanced tools
Comparing version 0.2.2 to 0.3.0
@@ -37,2 +37,26 @@ /*! | ||
} | ||
}, | ||
disallowEmptyDocumentation: { | ||
src: 'test/disallowEmptyDocumentation', | ||
options: { | ||
disallowEmptyDocumentation: false | ||
} | ||
}, | ||
disallowUnusedDocumentation: { | ||
src: 'test/disallowUnusedDocumentation', | ||
options: { | ||
disallowUnusedDocumentation: false | ||
} | ||
}, | ||
requireCompleteMessageDocumentation: { | ||
src: 'test/requireCompleteMessageDocumentation', | ||
options: { | ||
requireCompleteMessageDocumentation: false | ||
} | ||
}, | ||
requireMetadata: { | ||
src: 'test/requireMetadata', | ||
options: { | ||
requireMetadata: false | ||
} | ||
} | ||
@@ -39,0 +63,0 @@ }, |
# grunt-banana-checker Release History | ||
## v0.3.0 / 2015-09-01 | ||
* Fail if the target directory doesn't exist (James D. Forrester) | ||
* Allow individual checks to be disabled in config (James D. Forrester) | ||
* Be able to require complete translations, or specific messages in all translations (James D. Forrester) | ||
* build: Bump grunt-jscs to latest version (James D. Forrester) | ||
* Enforce disallowBlankTranslations, disallowDuplicateTranslations and disallowUnusedTranslations (James D. Forrester) | ||
## v0.2.2 / 2015-06-05 | ||
@@ -4,0 +11,0 @@ * Fix off-by-one error in counting the number of messages (Kunal Mehta) |
{ | ||
"name": "grunt-banana-checker", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"description": "A grunt checker for the \"banana\" JSON i18n system provided by MediaWiki and jquery.i18n", | ||
@@ -27,4 +27,4 @@ "scripts": { | ||
"grunt-contrib-watch": "0.6.1", | ||
"grunt-jscs": "1.8.0" | ||
"grunt-jscs": "2.1.0" | ||
} | ||
} |
@@ -37,3 +37,3 @@ [![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) | ||
### sourceFile | ||
#### sourceFile | ||
Type: `string` | ||
@@ -44,3 +44,3 @@ Default value: `"en.json"` | ||
### documentationFile | ||
#### documentationFile | ||
Type: `string` | ||
@@ -51,3 +51,59 @@ Default value: `"qqq.json"` | ||
#### requireMetadata | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to fail if message files don't have a `@metadata` meta-data key. | ||
#### requireCompleteMessageDocumentation | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to fail if any message is in the primary file but not documented. | ||
#### disallowEmptyDocumentation | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to fail if any message is in the primary file but documented as a blank string. | ||
#### disallowUnusedDocumentation | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to fail if any documented message isn't in the primary file. | ||
#### disallowBlankTranslations | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to fail if any message is translated as a blank string. | ||
#### disallowDuplicateTranslations | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to fail if any message is translated as identical to the original string. | ||
#### disallowUnusedTranslations | ||
Type: `boolean` | ||
Default value: `true` | ||
Whether to fail if any translated message isn't in the primary file. | ||
#### requireCompleteTranslationLanguages | ||
Type: `string[]` | ||
Default value: `[]` | ||
Example value: `[ 'fr', 'es' ]` | ||
Languages on which to fail if any message in the primary file is missing. | ||
#### requireCompleteTranslationMessages | ||
Type: `string[]` | ||
Default value: `[]` | ||
Example value: `[ 'first-message-key', 'third-message-key' ]` | ||
Messages on which to fail if missing in any provided language. | ||
Example uses | ||
@@ -54,0 +110,0 @@ -------------------- |
@@ -8,52 +8,147 @@ /*! | ||
grunt.registerMultiTask( 'banana', function () { | ||
var path = require( 'path' ), | ||
var ok, | ||
path = require( 'path' ), | ||
fs = require( 'fs' ), | ||
options = this.options( { | ||
sourceFile: 'en.json', | ||
documentationFile: 'qqq.json' | ||
documentationFile: 'qqq.json', | ||
requireMetadata: true, | ||
requireCompleteMessageDocumentation: true, | ||
disallowUnusedDocumentation: true, | ||
disallowBlankTranslations: true, | ||
disallowDuplicateTranslations: true, | ||
disallowUnusedTranslations: true, | ||
requireCompleteTranslationLanguages: [], | ||
requireCompleteTranslationMessages: [] | ||
} ), | ||
messageCount = 0, | ||
ok = true; | ||
messageCount = 0; | ||
if ( this.filesSrc.length === 0 ) { | ||
grunt.log.error( 'Target directory does not exist.' ); | ||
return false; | ||
} | ||
ok = true; | ||
this.filesSrc.forEach( function ( dir ) { | ||
var documentationMessagesMetadataIndex, | ||
sourceMessagesMetadataIndex, | ||
message, | ||
documentationIndex, | ||
documentationMessages = grunt.file.readJSON( path.resolve( dir, options.documentationFile ) ), | ||
documentationMessageKeys = Object.keys( documentationMessages ), | ||
var message, index, offset, | ||
// Source message data | ||
sourceMessages, sourceMessageKeys, | ||
// Documentation message data | ||
documentationMessages, documentationMessageKeys, | ||
// Translated message data | ||
translatedFiles, | ||
translatedData = {}, | ||
documentationMessageBlanks = [], | ||
sourceMessageMissing = [], | ||
sourceMessages = grunt.file.readJSON( path.resolve( dir, options.sourceFile ) ), | ||
sourceMessageKeys = Object.keys( sourceMessages ), | ||
count = 0; | ||
sourceMessagesMetadataIndex = sourceMessageKeys.indexOf( '@metadata' ); | ||
if ( sourceMessagesMetadataIndex === -1 ) { | ||
grunt.log.error( 'Source file lacks a metadata block.' ); | ||
ok = false; | ||
return; | ||
function messages( filename, type ) { | ||
var messageArray; | ||
try { | ||
messageArray = grunt.file.readJSON( path.resolve( dir, filename ) ); | ||
} catch ( e ) { | ||
grunt.log.error( 'Loading ' + type + ' messages failed: "' + e + '".' ); | ||
ok = false; | ||
throw e; | ||
} | ||
return messageArray; | ||
} | ||
sourceMessageKeys.splice( sourceMessagesMetadataIndex, 1 ); | ||
function keysNoMetadata( messageArray, type ) { | ||
var keys, offset; | ||
try { | ||
keys = Object.keys( messageArray ); | ||
} catch ( e ) { | ||
grunt.log.error( 'Loading ' + type + ' messages failed: "' + e + '".' ); | ||
ok = false; | ||
throw e; | ||
} | ||
offset = keys.indexOf( '@metadata' ); | ||
if ( offset === -1 ) { | ||
if ( options.requireMetadata ) { | ||
grunt.log.error( 'No metadata block in the ' + type + ' messages file.' ); | ||
ok = false; | ||
} | ||
} else { | ||
keys.splice( offset, 1 ); | ||
} | ||
return keys; | ||
} | ||
sourceMessages = messages( options.sourceFile, 'source' ); | ||
sourceMessageKeys = keysNoMetadata( sourceMessages, 'source' ); | ||
documentationMessages = messages( options.documentationFile, 'documentation' ); | ||
documentationMessageKeys = keysNoMetadata( documentationMessages, 'documentation' ); | ||
// Count after @metadata is removed | ||
messageCount += sourceMessageKeys.length; | ||
documentationMessagesMetadataIndex = documentationMessageKeys.indexOf( '@metadata' ); | ||
if ( documentationMessagesMetadataIndex === -1 ) { | ||
grunt.log.error( 'Documentation file lacks a metadata block.' ); | ||
ok = false; | ||
return; | ||
} | ||
documentationMessageKeys.splice( documentationMessagesMetadataIndex, 1 ); | ||
translatedFiles = fs.readdirSync( dir ).filter( function ( value ) { | ||
return ( | ||
value !== options.sourceFile && | ||
value !== options.documentationFile && | ||
value.match( /.*.json/ ) | ||
); | ||
} ); | ||
translatedFiles.forEach( function ( languageFile ) { | ||
var language = languageFile.match( /(.*)\.json$/ )[ 1 ], | ||
languageMesages = messages( languageFile, language ), | ||
keys = keysNoMetadata( languageMesages, language ), | ||
blanks = [], | ||
duplicates = [], | ||
unuseds = [], | ||
missing = sourceMessageKeys.slice( 0 ); | ||
for ( index in keys ) { | ||
message = keys[ index ]; | ||
if ( missing.indexOf( message ) !== -1 ) { | ||
if ( languageMesages[ message ] === sourceMessages[ message ] ) { | ||
duplicates.push( message ); | ||
} | ||
missing.splice( missing.indexOf( message ), 1 ); | ||
} else { | ||
unuseds.push( message ); | ||
} | ||
if ( typeof languageMesages[ message ] !== 'string' ) { | ||
continue; | ||
} | ||
if ( languageMesages[ message ].trim() === '' ) { | ||
blanks.push( message ); | ||
} | ||
} | ||
translatedData[ language ] = { | ||
messages: languageMesages, | ||
keys: keys, | ||
blank: blanks, | ||
duplicate: duplicates, | ||
unused: unuseds, | ||
missing: missing | ||
}; | ||
} ); | ||
while ( sourceMessageKeys.length > 0 ) { | ||
message = sourceMessageKeys[0]; | ||
documentationIndex = documentationMessageKeys.indexOf( message ); | ||
message = sourceMessageKeys[ 0 ]; | ||
if ( documentationIndex !== -1 ) { | ||
offset = documentationMessageKeys.indexOf( message ); | ||
if ( documentationMessages[message].trim() === '' ) { | ||
if ( offset !== -1 ) { | ||
if ( documentationMessages[ message ].trim() === '' ) { | ||
documentationMessageBlanks.push( message ); | ||
} | ||
documentationMessageKeys.splice( documentationIndex, 1 ); | ||
documentationMessageKeys.splice( offset, 1 ); | ||
} else { | ||
@@ -65,40 +160,152 @@ sourceMessageMissing.push( message ); | ||
count = sourceMessageMissing.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
if ( options.requireCompleteMessageDocumentation ) { | ||
count = sourceMessageMissing.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( | ||
count + ' message' + ( count > 1 ? 's lack' : ' lacks' ) + ' documentation.' | ||
); | ||
grunt.log.error( | ||
count + ' message' + ( count > 1 ? 's lack' : ' lacks' ) + ' documentation.' | ||
); | ||
sourceMessageMissing.forEach( function ( message ) { | ||
grunt.log.error( 'Message "' + message + '" lacks documentation.' ); | ||
} ); | ||
sourceMessageMissing.forEach( function ( message ) { | ||
grunt.log.error( 'Message "' + message + '" lacks documentation.' ); | ||
} ); | ||
} | ||
} | ||
count = documentationMessageBlanks.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
if ( options.disallowEmptyDocumentation ) { | ||
count = documentationMessageBlanks.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( | ||
count + ' documented message' + ( count > 1 ? 's are' : ' is' ) + ' blank.' | ||
); | ||
grunt.log.error( | ||
count + ' documented message' + ( count > 1 ? 's are' : ' is' ) + ' blank.' | ||
); | ||
documentationMessageBlanks.forEach( function ( message ) { | ||
grunt.log.error( 'Message "' + message + '" is documented with a blank string.' ); | ||
} ); | ||
documentationMessageBlanks.forEach( function ( message ) { | ||
grunt.log.error( 'Message "' + message + '" is documented with a blank string.' ); | ||
} ); | ||
} | ||
} | ||
count = documentationMessageKeys.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
if ( options.disallowUnusedDocumentation ) { | ||
count = documentationMessageKeys.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( | ||
count + ' documented message' + ( count > 1 ? 's are' : ' is' ) + ' undefined.' | ||
); | ||
grunt.log.error( | ||
count + ' documented message' + ( count > 1 ? 's are' : ' is' ) + ' undefined.' | ||
); | ||
documentationMessageKeys.forEach( function ( message ) { | ||
grunt.log.error( 'Message "' + message + '" is documented but undefined.' ); | ||
} ); | ||
documentationMessageKeys.forEach( function ( message ) { | ||
grunt.log.error( 'Message "' + message + '" is documented but undefined.' ); | ||
} ); | ||
} | ||
} | ||
for ( index in translatedData ) { | ||
if ( !translatedData.hasOwnProperty( index ) ) { | ||
continue; | ||
} | ||
if ( options.disallowBlankTranslations ) { | ||
count = translatedData[ index ].blank.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( 'The "' + index + '" translation has ' + count + ' blank translation' + ( count > 1 ? 's' : '' ) + ':' ); | ||
// jshint -W083 | ||
translatedData[ index ].blank.forEach( function ( message ) { | ||
grunt.log.error( 'The translation of "' + message + '" is blank.' ); | ||
} ); | ||
// jshint +W083 | ||
} | ||
} | ||
if ( options.disallowDuplicateTranslations ) { | ||
count = translatedData[ index ].duplicate.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( 'The "' + index + '" translation has ' + count + ' duplicate translation' + ( count > 1 ? 's' : '' ) + ':' ); | ||
// jshint -W083 | ||
translatedData[ index ].duplicate.forEach( function ( message ) { | ||
grunt.log.error( 'The translation of "' + message + '" is a duplicate of the primary message.' ); | ||
} ); | ||
// jshint +W083 | ||
} | ||
} | ||
if ( options.disallowUnusedTranslations ) { | ||
count = translatedData[ index ].unused.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( 'The "' + index + '" translation has ' + count + ' unused translation' + ( count > 1 ? 's' : '' ) + ':' ); | ||
// jshint -W083 | ||
translatedData[ index ].unused.forEach( function ( message ) { | ||
grunt.log.error( 'The translation of "' + message + '" is unused.' ); | ||
} ); | ||
// jshint +W083 | ||
} | ||
} | ||
} | ||
if ( options.requireCompleteTranslationLanguages.length ) { | ||
for ( index in translatedData ) { | ||
if ( | ||
!translatedData.hasOwnProperty( index ) || | ||
( options.requireCompleteTranslationLanguages.indexOf( index ) === -1 ) | ||
) { | ||
continue; | ||
} | ||
count = translatedData[ index ].missing.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( 'The "' + index + '" translation has ' + count + ' missing translation' + ( count > 1 ? 's' : '' ) + ':' ); | ||
// jshint -W083 | ||
translatedData[ index ].missing.forEach( function ( message ) { | ||
grunt.log.error( 'The translation of "' + message + '" is missing.' ); | ||
} ); | ||
// jshint +W083 | ||
} | ||
} | ||
} | ||
if ( options.requireCompleteTranslationMessages.length ) { | ||
for ( index in translatedData ) { | ||
if ( !translatedData.hasOwnProperty( index ) ) { | ||
continue; | ||
} | ||
for ( message in translatedData[ index ].missing ) { | ||
if ( !translatedData[ index ].missing.hasOwnProperty( sourceMessageKeys[ message ] ) ) { | ||
continue; | ||
} | ||
offset = options.requireCompleteTranslationMessages.indexOf( sourceMessageKeys[ message ] ); | ||
if ( offset === -1 ) { | ||
translatedData[ index ].missing.splice( offset, 1 ); | ||
} | ||
} | ||
count = translatedData[ index ].missing.length; | ||
if ( count > 0 ) { | ||
ok = false; | ||
grunt.log.error( 'The "' + index + '" translation is missing ' + count + ' required message' + ( count > 1 ? 's' : '' ) + ':' ); | ||
// jshint -W083 | ||
translatedData[ index ].missing.forEach( function ( message ) { | ||
grunt.log.error( 'The required message "' + message + '" is missing.' ); | ||
} ); | ||
// jshint +W083 | ||
} | ||
} | ||
} | ||
} ); | ||
@@ -105,0 +312,0 @@ |
@@ -9,3 +9,3 @@ { | ||
"third-message-key": "third message definition", | ||
"four-message-key": "four message definition" | ||
} | ||
"fourth-message-key": "fourth message definition" | ||
} |
@@ -8,3 +8,3 @@ { | ||
"third-message-key": "third message value", | ||
"four-message-key": "four message value" | ||
} | ||
"fourth-message-key": "fourth message value" | ||
} |
@@ -9,3 +9,3 @@ { | ||
"third-message-key": "third message definition", | ||
"four-message-key": "four message definition" | ||
} | ||
"fourth-message-key": "fourth message definition" | ||
} |
@@ -8,3 +8,3 @@ { | ||
"third-message-key": "third message value", | ||
"four-message-key": "four message value" | ||
} | ||
"fourth-message-key": "fourth message value" | ||
} |
@@ -8,3 +8,3 @@ { | ||
"third-message-key": "third message value", | ||
"four-message-key": "four message value" | ||
} | ||
"fourth-message-key": "fourth message value" | ||
} |
@@ -9,3 +9,3 @@ { | ||
"third-message-key": "third message definition", | ||
"four-message-key": "four message definition" | ||
} | ||
"fourth-message-key": "fourth message definition" | ||
} |
Sorry, the diff of this file is not supported yet
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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
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
22843
27
472
143
1
1