grunt-banana-checker
Advanced tools
| { | ||
| "@metadata": { | ||
| "metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "first message value", | ||
| "second-message-key": "second message value", | ||
| "third-message-key": "third message value", | ||
| "four-message-key": "four message value" | ||
| } |
| { | ||
| "@metadata": { | ||
| "first-metadata-key": "metadata value", | ||
| "second-metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "first message definition", | ||
| "second-message-key": "", | ||
| "third-message-key": "third message definition", | ||
| "four-message-key": "four message definition" | ||
| } |
| { | ||
| "@metadata": { | ||
| "metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "first message value", | ||
| "second-message-key": "second message value", | ||
| "third-message-key": "third message value", | ||
| "four-message-key": "four message value" | ||
| } |
| { | ||
| "@metadata": { | ||
| "first-metadata-key": "metadata value", | ||
| "second-metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "first message definition", | ||
| "second-message-key": "second message definition", | ||
| "third-message-key": "third message definition", | ||
| "four-message-key": "four message definition", | ||
| "five-message-key": "extra message definition" | ||
| } |
| { | ||
| "@metadata": { | ||
| "metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "first message value", | ||
| "second-message-key": "second message value", | ||
| "third-message-key": "third message value", | ||
| "four-message-key": "four message value" | ||
| } |
| { | ||
| "@metadata": { | ||
| "first-metadata-key": "metadata value", | ||
| "second-metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "first message definition", | ||
| "second-message-key": "second message definition", | ||
| "four-message-key": "four message definition" | ||
| } |
| { | ||
| "first-message-key": "first message value", | ||
| "second-message-key": "second message value", | ||
| "third-message-key": "third message value", | ||
| "four-message-key": "four message value" | ||
| } |
| { | ||
| "first-message-key": "first message definition", | ||
| "second-message-key": "second message definition", | ||
| "third-message-key": "third message definition", | ||
| "four-message-key": "four message definition" | ||
| } |
| { | ||
| "@metadata": { | ||
| "metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "erste Nachricht Wert", | ||
| "second-message-key": "zweiten Nachricht Wert", | ||
| "third-message-key": "dritten Nachricht Wert", | ||
| "fourth-message-key": "vierten Nachricht Wert" | ||
| } |
| { | ||
| "@metadata": { | ||
| "metadata-key": "metadata value" | ||
| }, | ||
| "first-message-key": "première valeur de message", | ||
| "second-message-key": "deuxième valeur de message", | ||
| "third-message-key": "troisième valeur de message", | ||
| "fourth-message-key": "quatrième valeur de message" | ||
| } |
+3
-1
@@ -9,2 +9,4 @@ language: node_js | ||
| channels: | ||
| - "chat.freenode.net#mediawiki-visualeditor" | ||
| - "chat.freenode.net#wikimedia-dev" | ||
| template: | ||
| - "%{repository}#%{build_number} (%{branch} - %{commit} %{author}): %{message} - %{build_url}" |
+24
-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 @@ }, |
+7
-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) |
+2
-2
| { | ||
| "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" | ||
| } | ||
| } |
+58
-2
@@ -37,3 +37,3 @@ [](http://badge.fury.io/js/grunt-banana-checker) [](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 @@ -------------------- |
+264
-57
@@ -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" | ||
| } |
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
22843
88.72%27
58.82%472
153.76%143
64.37%1
Infinity%1
Infinity%