diff --git a/examples/samples/unformatted.json b/examples/samples/unformatted.json new file mode 100644 index 0000000..2bc8f58 --- /dev/null +++ b/examples/samples/unformatted.json @@ -0,0 +1,3 @@ + { + "hello": "world" + } \ No newline at end of file diff --git a/src/index.js b/src/index.js index 847fd15..f4343ba 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,7 @@ const AllowComments = 'allowComments'; const fileLintResults = {}; const fileComments = {}; const fileDocuments = {}; +const fileLengths = {}; const getSignature = problem => `${problem.range.start.line} ${problem.range.start.character} ${problem.message}`; @@ -104,6 +105,30 @@ const errorSignature = err => const getErrorCode = _.pipe(_.get('ruleId'), _.split('/'), _.last); +const preprocessorPlaceholder = '___'; +const preprocessorTemplate = `JSON.stringify(${preprocessorPlaceholder})`; + +function mapFix(fix, fileLength, prefixLength, suffix) { + let text = fix.text; + // We have to map the fix in such a way, that we account for the removed prefix and suffix. + let range = fix.range.map(location => location - prefixLength); + // For the suffix we have three cases: + // 1) The fix ends before the suffix => nothing left to do + if (range[0] >= fileLength + suffix.length) { + // 2) The fix starts after the suffix (for example concerning the last line break) + range = range.map(location => location - suffix.length); + } else if (range[1] >= fileLength) { + // 3) The fix intersects the suffix + range[1] = Math.max(range[1] - suffix.length, fileLength); + // in that case we have to delete the suffix also from the fix text. + const suffixPosition = text.lastIndexOf(suffix); + if (suffixPosition >= 0) { + text = text.slice(0, suffixPosition) + text.slice(suffixPosition + suffix.length); + } + } + return {range, text}; +} + const processors = { '.json': { preprocess: function(text, fileName) { @@ -112,12 +137,31 @@ const processors = { const parsed = jsonServiceHandle.parseJSONDocument(textDocument); fileLintResults[fileName] = getDiagnostics(parsed); fileComments[fileName] = parsed.comments; - return ['']; // sorry nothing ;) + + const [, eol = ''] = text.match(/([\n\r]*)$/); + fileLengths[fileName] = text.length - eol.length; + return [ + { + text: + preprocessorTemplate.replace( + preprocessorPlaceholder, + text.slice(0, fileLengths[fileName]) + ) + eol, + filename: 'extracted.json' + } + ]; }, postprocess: function(messages, fileName) { const textDocument = fileDocuments[fileName]; + const fileLength = fileLengths[fileName]; delete fileLintResults[fileName]; delete fileComments[fileName]; + + const prefixLength = preprocessorTemplate.indexOf(preprocessorPlaceholder); + const suffix = preprocessorTemplate.slice( + prefixLength + preprocessorPlaceholder.length + ); + return _.pipe( _.first, _.groupBy(errorSignature), @@ -144,9 +188,23 @@ const processors = { endColumn: error.endColumn + 1 }); }), + _.mapValues(error => { + if (_.startsWith('json/', error.ruleId)) return error; + + const newError = _.assign(error, { + column: error.column - (error.line === 1 ? prefixLength : 0), + endColumn: error.endColumn - (error.endLine === 1 ? prefixLength : 0) + }); + if (error.fix) { + newError.fix = mapFix(error.fix, fileLength, prefixLength, suffix); + } + + return newError; + }), _.values )(messages); - } + }, + supportsAutofix: true } }; diff --git a/test/.eslintrc.with-prettier.json b/test/.eslintrc.with-prettier.json new file mode 100644 index 0000000..c3e358d --- /dev/null +++ b/test/.eslintrc.with-prettier.json @@ -0,0 +1,12 @@ +{ + "plugins": ["self", "prettier"], + "rules": { + "prettier/prettier": ["error", { + "tabWidth": 4, + "useTabs": true, + "trailingComma": "all", + "singleQuote": true, + "endOfLine": "lf" + }] + } +} \ No newline at end of file diff --git a/test/integration.test.js b/test/integration.test.js index dcbd756..12f7d8f 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -5,11 +5,18 @@ const _ = require('lodash/fp'); const SCOPE = 'self'; // (for test purpose only, relying the the eslint-plugin-self for tests) const scoped = rule => `${SCOPE}/${rule}`; -function getLintResults(filename, eslintConfig) { +function getLintResults(filename, eslintConfig, commands = []) { try { const results = execFileSync( 'eslint', - ['--config', eslintConfig || 'custom.eslintrc.json', '--format', 'json', filename], + [ + '--config', + eslintConfig || 'custom.eslintrc.json', + ...commands, + '--format', + 'json', + filename + ], { encoding: 'utf8', stdio: 'pipe', @@ -45,10 +52,9 @@ function validateInfringementExpectation(expected, actualSituation) { else expect(actualSituation).to.have.property(scoped(rule)); } const allExpectedErrors = expected.map(_.pipe(_.split(':'), _.head, scoped)); - expect(_.xor(_.keys(actualSituation), allExpectedErrors)).to.have.length( - 0, - 'Extra errors found' - ); + // only check for errors generated by this plugin + const actualErrors = _.keys(actualSituation).filter(error => error.startsWith(scoped(''))); + expect(_.xor(actualErrors, allExpectedErrors)).to.have.length(0, 'Extra errors found'); } function validateFile(filename, expectations = {}) { @@ -66,6 +72,15 @@ function validateFile(filename, expectations = {}) { ); } +function validateFixes(filename, config = {}) { + const result = getLintResults(`samples/${filename}.json`, config.eslintrc, ['--fix-dry-run']); + + expect(result.output).not.to.be.undefined; + if (config.fixedOutput !== undefined) { + expect(result.output).to.equal(config.fixedOutput); + } +} + describe('Integrations tests', function() { it('validate correct json', function() { validateFile('good-json', {errorCount: 0, warningCount: 0}); @@ -122,3 +137,12 @@ describe('Integrations tests with config', function() { }); }); }); + +describe('Integrations tests with Prettier', function() { + it('prettifies JSON file', function() { + validateFixes('unformatted', { + eslintrc: '.eslintrc.with-prettier.json', + fixedOutput: '{\n\t"hello": "world"\n}\n' + }); + }); +}); diff --git a/test/unit.test.js b/test/unit.test.js index 47a83d9..a53a89d 100644 --- a/test/unit.test.js +++ b/test/unit.test.js @@ -27,12 +27,13 @@ describe('plugin', function() { }); describe('preprocess', function() { const preprocess = plugin.processors['.json'].preprocess; - it('should return the same text', function() { + it('should contain the text', function() { const fileName = 'whatever-the-name.js'; - const newText = preprocess('whatever', fileName); + const text = 'whatever'; + const newText = preprocess(text, fileName); assert.isArray(newText, 'preprocess should return array'); - assert.strictEqual(newText[0], ''); + assert.include(newText[0], text); }); }); describe('postprocess', function() {