Repository: azeemba/eslint-plugin-json Branch: master Commit: b381b1583161 Files: 36 Total size: 42.4 KB Directory structure: gitextract_qzrczb7v/ ├── .eslintignore ├── .eslintrc ├── .github/ │ └── workflows/ │ └── node.js.yml ├── .gitignore ├── .npmignore ├── .nycrc ├── LICENSE.txt ├── README.md ├── examples/ │ ├── .eslintrc │ ├── package.json │ └── samples/ │ ├── duplicate-keys.json │ ├── good-json.json │ ├── json-with-comments.json │ ├── whole-mess.json │ └── wrong-syntax.json ├── package.json ├── src/ │ └── index.js ├── test/ │ ├── .eslintrc.with-recommended-comments-config.mjs │ ├── .eslintrc.with-recommended-comments-legacy-config.json │ ├── .eslintrc.with-recommended-config.mjs │ ├── .eslintrc.with-recommended-legacy-config.json │ ├── custom.eslintrc-legacy.json │ ├── custom.eslintrc.config.mjs │ ├── integration-across-eslint-majors.sh │ ├── integration-legacy.test.js │ ├── integration.test.js │ ├── packages/ │ │ ├── eslint-v7-legacy/ │ │ │ └── package.json │ │ ├── eslint-v8/ │ │ │ └── package.json │ │ ├── eslint-v8-legacy/ │ │ │ └── package.json │ │ └── eslint-v9/ │ │ └── package.json │ └── unit.test.js └── vendor/ └── eslint-plugin-self/ ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── index.js └── package.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ test/packages ================================================ FILE: .eslintrc ================================================ { "env": { "node": true, "mocha": true, "es6": true }, "parserOptions": { "ecmaVersion": 2017 }, "plugins": ["prettier"], "extends": ["eslint:recommended"], "rules": { "prettier/prettier": ["warn", { "singleQuote": true, "tabWidth": 4, "printWidth": 100, "bracketSpacing": false }], "no-console": "error" }, "overrides": [ { "files": "*.mjs", "parserOptions": { "sourceType": "module" } } ] } ================================================ FILE: .github/workflows/node.js.yml ================================================ # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Build on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: node-version: [18.x, 20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm ci - run: npm run lint - run: npm run test - run: npm run integration ci 7-legacy 8-legacy 8 9 # eslint versions - run: npm run integration test 7-legacy 8-legacy 8 9 # eslint versions - name: Upload coverage to Codecov uses: codecov/codecov-action@v2 with: verbose: true directory: coverage ================================================ FILE: .gitignore ================================================ node_modules private tmp coverage .nyc_output example/package-lock.json .vscode/ .idea/ ================================================ FILE: .npmignore ================================================ test examples private tmp coverage .npmignore .eslintrc .github .vscode .nycrc .eslintignore vendor ================================================ FILE: .nycrc ================================================ { "all": true, "include": ["src"], "reporter": ["text", "text-summary", "lcov"] } ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) 2015-2021 Azeem Bande-Ali Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # eslint-plugin-json [![npm](https://img.shields.io/npm/v/eslint-plugin-json.svg)](https://www.npmjs.com/package/eslint-plugin-json) [![Build](https://github.com/azeemba/eslint-plugin-json/workflows/Build/badge.svg)](https://github.com/azeemba/eslint-plugin-json/actions/workflows/node.js.yml) [![codecov](https://codecov.io/gh/azeemba/eslint-plugin-json/branch/master/graph/badge.svg)](https://codecov.io/gh/azeemba/eslint-plugin-json) [![Code Climate](https://codeclimate.com/github/azeemba/eslint-plugin-json/badges/gpa.svg)](https://codeclimate.com/github/azeemba/eslint-plugin-json) > Eslint plugin for JSON files :warning: If you are using eslint v9 or newer, use eslint-plugin-json v4 or newer. ## Installation Install `eslint-plugin-json` along [`eslint`](http://eslint.org): ```shell $ npm install --save-dev eslint eslint-plugin-json # or $ yarn add --dev eslint eslint-plugin-json ``` **Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-json` globally. ## Usage ### Basic configuration (Flat Config ESLint Format) The `json` plugin ship with two recommended config you can use to easily activate it via the `extends` key. It comes in two flavor: one strict (`recommended`) and one allowing comments `recommended-with-comments`. ```js import json from 'eslint-plugin-json'; export default [ { files: ["**/*.json"], ...json.configs["recommended"] } ]; ``` ### Basic configuration (Legacy ESLint Format) The `json` plugin ship with two recommended config you can use to easily activate it via the `extends` key. It comes in two flavor: one strict (`recommended-legacy`) and one allowing comments `recommended-with-comments-legacy`. ```json { "extends": ["plugin:json/recommended-legacy"] } ``` You can run ESLint on individual JSON files or you can use the `--ext` flag to add JSON files to the list. ``` eslint . --ext .json,.js eslint example.json ``` ### Custom Configuration (Flat Config ESLint Format) If you want more granular control over which rules, and which severity you want. If you want them all, add the `json/json` rule (or its alias `json/*`). (this is what the `recommended` config does) #### Global rules The global rules (`json/json` or its alias `json/*`) activate all the rules. Note it can be configured to ignore errors cause by comments. To do so, add option `'allowComments'` or `{allowComments: true}` For instance: ```js import json from "eslint-plugin-json"; export default [ { files: ["**/*.json"], plugins: { json }, processor: "json/json" "rules": { "json/*": ["error", "allowComments"], // or the equivalent: "json/*": ["error", {"allowComments": true}] } }, ]; ``` ### Custom Configuration (Legacy ESLint Format) If you want more granular control over which rules, and which severity you want. Add `json` to the list of plugins (You can omit the `eslint-plugin-` prefix) Then pick your rules. If you want them all, add the `json/json` rule (or its alias `json/*`). (this is what the `recommended-legacy` config does) #### Global rules The global rules (`json/json` or its alias `json/*`) activate all the rules. Note it can be configured to ignore errors cause by comments. To do so, add option `'allowComments'` or `{allowComments: true}` For instance: ```json { "plugins": [ "json" ], "rules": { "json/*": ["error", "allowComments"], // or the equivalent: "json/*": ["error", {"allowComments": true}] } } ``` #### Individual Rules Here is the list of individual rules (with name in `kebab-case`)in case you want granular error/warning level: - `json/undefined` - `json/enum-value-mismatch` - `json/unexpected-end-of-comment` - `json/unexpected-end-of-string` - `json/unexpected-end-of-number` - `json/invalid-unicode` - `json/invalid-escape-character` - `json/invalid-character` - `json/property-expected` - `json/comma-expected` - `json/colon-expected` - `json/value-expected` - `json/comma-or-close-backet-expected` - `json/comma-or-close-brace-expected` - `json/trailing-comma` - `json/duplicate-key` - `json/comment-not-permitted` - `json/schema-resolve-error` - `json/unknown` (error that does not match previous ones) ## FAQs #### How does eslint-plugin-json work? Starting from version 1.3, this plugin relies on what [VSCode](https://github.com/Microsoft/vscode-json-languageservice) uses for its implementation of JSON validation. Originaly this plugin used to use JSHint, however due to heavy dependencies, it was replaced. #### Why doesn't this plugin use `eslint` itself or just `JSON.parse`? `eslint`'s parser is a JavaScript parser. JSON is a stricter subset and things that are valid JavaScript are not valid JSON. This is why something more specific is more appropriate. While `JSON.parse` seems ideal, it is not designed to continue after the first error. So if you have a missing trailing comma in the start of the file, the rest of the file will go unlinted. A smarter parser that can self-correct after seeing errors is needed which the VSCode implementation provides by leveraging the [jsonc-parser](https://www.npmjs.com/package/jsonc-parser) module. #### Will this plugin provide more configuration? It is now possible as you can see in the [Configuration section](#custom-configuration). Additionally, support for autofixing common errors could be added in the feature. #### Is `eslint` really the best tool to lint my JSON? Not really. `eslint` plugin interface wasn't designed to lint a completely different language but its interface is flexible enough to allow it. So this plugin is certainly unusual. Ideally, your editor would natively supports linting JSON. If it doesn't though, then might as well use this plugin. Hacky linting is better than no linting :). ================================================ FILE: examples/.eslintrc ================================================ { "plugins": ["json"], "rules": { "json/*": ["warn"], "json/duplicate-key": "error", "json/trailing-comma": "error" }, "overrides": [ { "files": ["samples/json-with-comments.json"], "rules": { "json/*": ["warn", {"allowComments": true}] } } ] } ================================================ FILE: examples/package.json ================================================ { "name": "eslint-plugin-json-example", "version": "1.0.0", "description": "Some example of usage of plugin", "main": "index.js", "scripts": { "lint": "eslint", "test": "node integration-test.js" }, "keywords": [], "license": "ISC", "devDependencies": { "eslint": "^6.3.0", "eslint-plugin-json": ".." } } ================================================ FILE: examples/samples/duplicate-keys.json ================================================ { "iam": "here", "iam": "and here" } ================================================ FILE: examples/samples/good-json.json ================================================ { "hello": "world" } ================================================ FILE: examples/samples/json-with-comments.json ================================================ { "hello": "world" // with a comment } ================================================ FILE: examples/samples/whole-mess.json ================================================ { "iam": "here", "iam": "and here", // COMMENT } ================================================ FILE: examples/samples/wrong-syntax.json ================================================ { "oops": } ================================================ FILE: package.json ================================================ { "name": "eslint-plugin-json", "version": "4.0.1", "description": "eslint plugin for JSON files", "keywords": [ "eslint", "eslintplugin", "eslint-plugin", "json", "eslint-plugin-json" ], "author": "Azeem Bande-Ali ", "contributors": [ "Adriean Khisbe (https://github.com/AdrieanKhisbe/)" ], "main": "src/index.js", "scripts": { "integration": "test/integration-across-eslint-majors.sh", "test": "nyc mocha test/unit.test.js", "lint": "eslint src test", "eslint": "eslint" }, "repository": { "type": "git", "url": "git+https://github.com/azeemba/eslint-plugin-json.git" }, "bugs": { "url": "https://github.com/azeemba/eslint-plugin-json/issues" }, "dependencies": { "lodash": "^4.17.21", "vscode-json-languageservice": "^4.1.6" }, "devDependencies": { "chai": "^4.3.4", "codecov": "^3.8.3", "eslint": "^8.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^3.4.1", "eslint-plugin-self": "file:vendor/eslint-plugin-self", "mocha": "^10.4.0", "nyc": "^15.1.0", "prettier": "^2.3.2" }, "engines": { "node": ">=18.0" }, "url": "https://github.com/azeemba/eslint-plugin-json", "license": "MIT" } ================================================ FILE: src/index.js ================================================ const _ = require('lodash/fp'); const jsonService = require('vscode-json-languageservice'); const jsonServiceHandle = jsonService.getLanguageService({}); const ErrorCodes = { Undefined: 0, EnumValueMismatch: 1, UnexpectedEndOfComment: 0x101, UnexpectedEndOfString: 0x102, UnexpectedEndOfNumber: 0x103, InvalidUnicode: 0x104, InvalidEscapeCharacter: 0x105, InvalidCharacter: 0x106, PropertyExpected: 0x201, CommaExpected: 0x202, ColonExpected: 0x203, ValueExpected: 0x204, CommaOrCloseBacketExpected: 0x205, CommaOrCloseBraceExpected: 0x206, TrailingComma: 0x207, DuplicateKey: 0x208, CommentNotPermitted: 0x209, SchemaResolveError: 0x300, }; const AllErrorCodes = _.values(ErrorCodes); const AllowComments = 'allowComments'; const fileLintResults = {}; const fileComments = {}; const fileDocuments = {}; const getSignature = (problem) => `${problem.range.start.line} ${problem.range.start.character} ${problem.message}`; function getDiagnostics(jsonDocument) { return _.pipe( _.map((problem) => [getSignature(problem), problem]), _.reverse, // reverse ensure fromPairs keep first signature occurence of problem _.fromPairs )(jsonDocument.syntaxErrors); } const reportError = (filter) => (errorName, context) => { _.filter(filter, fileLintResults[context.getFilename()]).forEach((error) => { context.report({ ruleId: `json/${errorName}`, message: error.message, loc: { start: {line: error.range.start.line + 1, column: error.range.start.character}, end: {line: error.range.end.line + 1, column: error.range.end.character}, }, // later: see how to add fix }); }); }; const reportComment = (errorName, context) => { const ruleOption = _.head(context.options); if (ruleOption === AllowComments || _.get(AllowComments, ruleOption)) return; _.forEach((comment) => { context.report({ ruleId: errorName, message: 'Comment not allowed', loc: { start: {line: comment.start.line + 1, column: comment.start.character}, end: {line: comment.end.line + 1, column: comment.end.character}, }, }); }, fileComments[context.getFilename()]); }; const ruleSchema = [ { anyOf: [ { enum: ['allowComments'], }, { type: 'object', properties: { allowComments: {type: 'boolean'}, }, additionalProperties: false, }, ], }, ]; const makeRule = (errorName, reporters) => ({ meta: {schema: ruleSchema}, create(context) { return { Program() { _.flatten([reporters]).map((reporter) => reporter(errorName, context)); }, }; }, }); const rules = _.pipe( _.mapKeys(_.kebabCase), _.toPairs, _.map(([errorName, errorCode]) => [ errorName, makeRule( errorName, reportError((err) => err.code === errorCode) ), ]), _.fromPairs, _.assign({ '*': makeRule('*', [reportError(_.constant(true)), reportComment]), json: makeRule('json', [reportError(_.constant(true)), reportComment]), unknown: makeRule('unknown', reportError(_.negate(AllErrorCodes.includes))), 'comment-not-permitted': makeRule('comment-not-permitted', reportComment), }) )(ErrorCodes); const errorSignature = (err) => ['message', 'line', 'column', 'endLine', 'endColumn'].map((field) => err[field]).join('::'); const getErrorCode = _.pipe(_.get('ruleId'), _.split('/'), _.last); const meta = { name: 'eslint-plugin-json', version: '3.1.0', }; const jsonProcessor = { preprocess: function (text, fileName) { const textDocument = jsonService.TextDocument.create(fileName, 'json', 1, text); fileDocuments[fileName] = textDocument; const parsed = jsonServiceHandle.parseJSONDocument(textDocument); fileLintResults[fileName] = getDiagnostics(parsed); fileComments[fileName] = parsed.comments; return ['']; // sorry nothing ;) }, postprocess: function (messages, fileName) { const textDocument = fileDocuments[fileName]; delete fileLintResults[fileName]; delete fileComments[fileName]; return _.pipe( _.first, _.groupBy(errorSignature), _.mapValues((errors) => { if (errors.length === 1) return _.first(errors); // Otherwise there is two errors: the generic and specific one // json/* or json/json and json/some-code const firstErrorCode = getErrorCode(errors[0]); const isFirstGeneric = ['*', 'json'].includes(firstErrorCode); const genericError = errors[isFirstGeneric ? 0 : 1]; const specificError = errors[isFirstGeneric ? 1 : 0]; return genericError.severity > specificError.severity ? genericError : specificError; }), _.mapValues((error) => { const source = textDocument.getText({ start: {line: error.line - 1, character: error.column}, end: {line: error.endLine - 1, character: error.endColumn}, }); return _.assign(error, { source, column: error.column + 1, endColumn: error.endColumn + 1, }); }), _.values )(messages); }, }; const processors = { // Supports old config. '.json': jsonProcessor, // Supports new config. json: jsonProcessor, }; const configs = { 'recommended-legacy': { plugins: ['json'], rules: { 'json/*': 'error', }, }, 'recommended-with-comments-legacy': { plugins: ['json'], rules: { 'json/*': ['error', {allowComments: true}], }, }, }; const json = {meta, rules, configs, processors}; json.configs['recommended'] = { files: ['**/*.json'], plugins: { json, }, rules: { 'json/*': 'error', }, processor: 'json/json', }; json.configs['recommended-with-comments'] = { files: ['**/*.json'], plugins: { json, }, rules: { 'json/*': ['error', {allowComments: true}], }, processor: 'json/json', }; module.exports = json; ================================================ FILE: test/.eslintrc.with-recommended-comments-config.mjs ================================================ import json from '../src/index.js'; export default [json.configs['recommended-with-comments']]; ================================================ FILE: test/.eslintrc.with-recommended-comments-legacy-config.json ================================================ { "extends": ["plugin:self/recommended-with-comments-legacy"] } ================================================ FILE: test/.eslintrc.with-recommended-config.mjs ================================================ import json from '../src/index.js'; export default [json.configs['recommended']]; ================================================ FILE: test/.eslintrc.with-recommended-legacy-config.json ================================================ { "extends": ["plugin:self/recommended-legacy"] } ================================================ FILE: test/custom.eslintrc-legacy.json ================================================ { "plugins": ["self"], "rules": { "self/*": "warn", "self/duplicate-key": "error", "self/trailing-comma": "error" }, "overrides": [ { "files": ["samples/json-with-comments.json"], "rules": { "self/*": ["warn", {"allowComments": true}] } }, { "files": ["samples/wrong-syntax.json"], "rules": { "self/*": "error" } } ] } ================================================ FILE: test/custom.eslintrc.config.mjs ================================================ import json from '../src/index.js'; export default [ { files: ['**/*.json'], plugins: {json}, processor: 'json/json', rules: { 'json/*': 'warn', 'json/duplicate-key': 'error', 'json/trailing-comma': 'error', }, }, { files: ['samples/json-with-comments.json'], plugins: {json}, rules: { 'json/*': ['warn', {allowComments: true}], }, }, { files: ['samples/wrong-syntax.json'], plugins: {json}, rules: { 'json/*': 'error', }, }, ]; ================================================ FILE: test/integration-across-eslint-majors.sh ================================================ #!/usr/bin/env bash set -e cmd="$1" shift case "$cmd" in test|install|ci);; *) echo "Unknown integration subcommand $cmd" exit 2 ;; esac if [[ -z "$@" ]]; then echo "No eslint major versions were provided" exit 2 fi echo "Will perform $cmd for following eslint majors lines: $@" for version in $@; do (cd test/packages/eslint-v$version && npm $cmd) done ================================================ FILE: test/integration-legacy.test.js ================================================ const {execFileSync} = require('child_process'); const {expect} = require('chai'); 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) { try { const results = execFileSync( 'eslint', [ '--config', eslintConfig || 'custom.eslintrc-legacy.json', '--format', 'json', filename, ], { encoding: 'utf8', stdio: 'pipe', cwd: __dirname, } ); return JSON.parse(results)[0]; } catch (err) { if (err.status !== 1 && err.status !== 0) throw new Error(`The lint command itself failed: ${err.status}, ${err.message}`); return JSON.parse(err.stdout)[0]; } } function groupInfringementsByRules(fileResults) { const errors = {}; const warnings = {}; for (const infringement of fileResults.messages) { const counter = infringement.severity === 1 ? warnings : errors; counter[infringement.ruleId] = (counter[infringement.ruleId] || 0) + 1; } return {errors, warnings}; } function validateInfringementExpectation(expected, actualSituation) { if (_.isEmpty(expected)) return; for (const someExpected of expected || []) { const [rule, expectedCount] = someExpected.split(':'); if (expectedCount) expect(actualSituation[scoped(rule)]).to.equal( Number(expectedCount), `unexpected count of rule ${rule}` ); 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' ); } function validateFile(filename, expectations = {}) { const results = getLintResults(`samples/${filename}.json`, expectations.eslintrc); const resultIndex = groupInfringementsByRules(results); validateInfringementExpectation(expectations.errors, resultIndex.errors, 'errors'); validateInfringementExpectation(expectations.warnings, resultIndex.warnings, 'warnings'); if (expectations.errorCount !== undefined) expect(results.errorCount).to.equal(expectations.errorCount, 'invalid count of errors'); if (expectations.warningCount !== undefined) expect(results.warningCount).to.equal( expectations.warningCount, 'invalid count of warnings' ); } describe('Integrations tests', function () { it('validate correct json', function () { validateFile('good-json', {errorCount: 0, warningCount: 0}); }); it('detect duplicate keys', function () { validateFile('duplicate-keys', { errors: ['duplicate-key:2'], }); // FIXME: give error count! }); it('handle comments in json', function () { validateFile('json-with-comments', {errorCount: 0, warningCount: 0}); }); it('detect wrong syntax', function () { validateFile('wrong-syntax', {errorCount: 1, warningCount: 0}); }); it('detect many infringements in messy json', function () { validateFile('whole-mess', { errors: ['duplicate-key:2', 'trailing-comma'], warnings: ['*'], }); }); }); describe('Integrations tests with config', function () { describe('recommended', function () { it('detect many infringements in messy json', function () { validateFile('whole-mess', { eslintrc: '.eslintrc.with-recommended-legacy-config.json', errors: ['*:4'], }); }); it('handle comments in json', function () { validateFile('json-with-comments', { eslintrc: '.eslintrc.with-recommended-legacy-config.json', errorCount: 1, // comment-not-permitted under the '*' glob }); }); }); describe('recommended-with-comments', function () { it('detect many infringements in messy json', function () { validateFile('whole-mess', { eslintrc: '.eslintrc.with-recommended-comments-legacy-config.json', errors: ['*:3'], }); }); it('handle comments in json', function () { validateFile('json-with-comments', { eslintrc: '.eslintrc.with-recommended-comments-legacy-config.json', errorCount: 0, warningCount: 0, }); }); }); }); ================================================ FILE: test/integration.test.js ================================================ const {execFileSync} = require('child_process'); const {expect} = require('chai'); const _ = require('lodash/fp'); const SCOPE = 'json'; // (for test purpose only) const scoped = (rule) => `${SCOPE}/${rule}`; function getLintResults(filename, eslintConfig) { try { const results = execFileSync( 'eslint', [ '--config', eslintConfig || 'custom.eslintrc.config.mjs', '--format', 'json', filename, ], { encoding: 'utf8', stdio: 'pipe', cwd: __dirname, } ); return JSON.parse(results)[0]; } catch (err) { if (err.status !== 1 && err.status !== 0) throw new Error(`The lint command itself failed: ${err.status}, ${err.message}`); return JSON.parse(err.stdout)[0]; } } function groupInfringementsByRules(fileResults) { const errors = {}; const warnings = {}; for (const infringement of fileResults.messages) { const counter = infringement.severity === 1 ? warnings : errors; counter[infringement.ruleId] = (counter[infringement.ruleId] || 0) + 1; } return {errors, warnings}; } function validateInfringementExpectation(expected, actualSituation) { if (_.isEmpty(expected)) return; for (const someExpected of expected || []) { const [rule, expectedCount] = someExpected.split(':'); if (expectedCount) expect(actualSituation[scoped(rule)]).to.equal( Number(expectedCount), `unexpected count of rule ${rule}` ); 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' ); } function validateFile(filename, expectations = {}) { const results = getLintResults(`samples/${filename}.json`, expectations.eslintrc); const resultIndex = groupInfringementsByRules(results); validateInfringementExpectation(expectations.errors, resultIndex.errors, 'errors'); validateInfringementExpectation(expectations.warnings, resultIndex.warnings, 'warnings'); if (expectations.errorCount !== undefined) expect(results.errorCount).to.equal(expectations.errorCount, 'invalid count of errors'); if (expectations.warningCount !== undefined) expect(results.warningCount).to.equal( expectations.warningCount, 'invalid count of warnings' ); } describe('Integrations tests', function () { it('validate correct json', function () { validateFile('good-json', {errorCount: 0, warningCount: 0}); }); it('detect duplicate keys', function () { validateFile('duplicate-keys', { errors: ['duplicate-key:2'], }); // FIXME: give error count! }); it('handle comments in json', function () { validateFile('json-with-comments', {errorCount: 0, warningCount: 0}); }); it('detect wrong syntax', function () { validateFile('wrong-syntax', {errorCount: 1, warningCount: 0}); }); it('detect many infringements in messy json', function () { validateFile('whole-mess', { errors: ['duplicate-key:2', 'trailing-comma'], warnings: ['*'], }); }); }); describe('Integrations tests with config', function () { describe('recommended', function () { it('detect many infringements in messy json', function () { validateFile('whole-mess', { eslintrc: '.eslintrc.with-recommended-config.mjs', errors: ['*:4'], }); }); it('handle comments in json', function () { validateFile('json-with-comments', { eslintrc: '.eslintrc.with-recommended-config.mjs', errorCount: 1, // comment-not-permitted under the '*' glob }); }); }); describe('recommended-with-comments', function () { it('detect many infringements in messy json', function () { validateFile('whole-mess', { eslintrc: '.eslintrc.with-recommended-comments-config.mjs', errors: ['*:3'], }); }); it('handle comments in json', function () { validateFile('json-with-comments', { eslintrc: '.eslintrc.with-recommended-comments-config.mjs', errorCount: 0, warningCount: 0, }); }); }); }); ================================================ FILE: test/packages/eslint-v7-legacy/package.json ================================================ { "name": "eslint-plugin-json-v7", "version": "0.1.0", "description": "Integration for eslint-plugin-json against ESLint v7", "private": true, "main": "index.js", "scripts": { "pretest": "eslint --version", "test": "../../../node_modules/.bin/mocha test/integration-legacy.test.js" }, "keywords": [ "eslint", "eslint-plugin", "eslint-plugin-json", "integration-tests" ], "license": "MIT", "devDependencies": { "eslint": "^7.32.0", "mocha": "^10.4.0" }, "dependencies": {} } ================================================ FILE: test/packages/eslint-v8/package.json ================================================ { "name": "eslint-plugin-json-v8", "version": "0.1.0", "description": "Integration for eslint-plugin-json against ESLint v8", "private": true, "main": "index.js", "scripts": { "pretest": "eslint --version", "test": "ESLINT_USE_FLAT_CONFIG=true ../../../node_modules/.bin/mocha test/integration.test.js" }, "keywords": [ "eslint", "eslint-plugin", "eslint-plugin-json", "integration-tests" ], "license": "MIT", "devDependencies": { "eslint": "^8.57.0", "mocha": "^10.4.0" }, "dependencies": {} } ================================================ FILE: test/packages/eslint-v8-legacy/package.json ================================================ { "name": "eslint-plugin-json-v8", "version": "0.1.0", "description": "Integration for eslint-plugin-json against ESLint v8", "private": true, "main": "index.js", "scripts": { "pretest": "eslint --version", "test": "../../../node_modules/.bin/mocha test/integration-legacy.test.js" }, "keywords": [ "eslint", "eslint-plugin", "eslint-plugin-json", "integration-tests" ], "license": "MIT", "devDependencies": { "eslint": "^8.0.0", "mocha": "^9.0.3" }, "dependencies": {} } ================================================ FILE: test/packages/eslint-v9/package.json ================================================ { "name": "eslint-plugin-json-v9", "version": "0.1.0", "description": "Integration for eslint-plugin-json against ESLint v9", "private": true, "main": "index.js", "scripts": { "pretest": "eslint --version", "test": "node_modules/.bin/mocha test/integration.test.js" }, "keywords": [ "eslint", "eslint-plugin", "eslint-plugin-json", "integration-tests" ], "license": "MIT", "devDependencies": { "eslint": "9.1.0", "mocha": "^10.4.0" }, "dependencies": {} } ================================================ FILE: test/unit.test.js ================================================ const plugin = require('../src'); const {assert} = require('chai'); const _ = require('lodash/fp'); describe('plugin', function () { describe('structure', function () { it('should contain processors object', function () { assert.property(plugin, 'processors', '.processors property is not defined'); }); it('should contain .json property', function () { assert.property(plugin.processors, '.json', '.json property is not defined'); }); it('should contain .json.preprocess property', function () { assert.property( plugin.processors['.json'], 'preprocess', '.json.preprocess is not defined' ); }); it('should contain .json.postprocess property', function () { assert.property( plugin.processors['.json'], 'postprocess', '.json.postprocess is not defined' ); }); }); describe('preprocess', function () { const preprocess = plugin.processors['.json'].preprocess; it('should return the same text', function () { const fileName = 'whatever-the-name.js'; const newText = preprocess('whatever', fileName); assert.isArray(newText, 'preprocess should return array'); assert.strictEqual(newText[0], ''); }); }); describe('postprocess', function () { const preprocess = plugin.processors['.json'].preprocess; const postprocess = plugin.processors['.json'].postprocess; const messageErrorFieldFromContextError = { ruleId: _.get('ruleId'), severity: _.getOr(1, 'severity'), message: _.get('message'), line: _.get('loc.start.line'), column: _.get('loc.start.column'), nodeType: _.getOr(null, 'nodeType'), endLine: _.get('loc.end.line'), endColumn: _.get('loc.end.column'), }; const convertContextErrorToMessageError = (err) => _.mapValues((extractor) => extractor(err), messageErrorFieldFromContextError); const fakeApplyRule = (rules) => (file) => { const errors = []; rules.forEach((rule) => { const xxx = rule.create({ getFilename() { return file; }, report(err) { errors.push(err); }, }); xxx.Program(); }); return [errors.map(convertContextErrorToMessageError)]; }; const singleQuotes = { fileName: 'singleQuotes.json', text: "{'x': 0}", }; const trailingCommas = { fileName: 'trailing.json', text: '{ "x": 0, }', }; const multipleErrors = { fileName: 'multipleErrors.json', text: "{ x: 200, 'what': 0 }", }; const trailingText = { fileName: 'trailingtext.json', text: '{ "my_string": "hello world" }' + ' \n' + 'bad_text', }; const good = { fileName: 'good.json', text: JSON.stringify({a: [1, 2, 3], b: 'cat', c: {x: 1}}), }; const rules = ['undefined', 'trailing-comma']; const lintFile = fakeApplyRule(rules.map((rule) => plugin.rules[rule])); const samples = [singleQuotes, trailingCommas, multipleErrors, trailingText, good]; samples.forEach((sample) => preprocess(sample.text, sample.fileName)); const errorsByFile = _.fromPairs( samples.map((sample) => [sample.fileName, lintFile(sample.fileName)]) ); it('should return an error for the single quotes', function () { const errors = postprocess(errorsByFile[singleQuotes.fileName], singleQuotes.fileName); assert.isArray(errors, 'should return an array'); assert.lengthOf(errors, 1, 'should return one error'); const error = errors[0]; assert.strictEqual(error.ruleId, 'json/undefined', 'should have a string ID'); assert.strictEqual(error.severity, 1, 'should have a numeric severity'); assert.strictEqual( error.message, 'Property keys must be doublequoted', 'should have a message' ); assert.strictEqual(error.line, 1, 'should point to first line'); assert.strictEqual(error.column, 2, 'should point to second character'); }); it('should return an error for trailing commas', function () { const errors = postprocess( errorsByFile[trailingCommas.fileName], trailingCommas.fileName ); assert.isArray(errors, 'should return an array'); assert.lengthOf(errors, 1, 'should return one error'); const error = errors[0]; assert.strictEqual(error.ruleId, 'json/trailing-comma', 'should have a string ID'); assert.strictEqual(error.line, 1, 'should point to the first line'); assert.strictEqual(error.column, 9, 'should point to the 9th character'); }); it('should report unrecoverable syntax error', function () { const errors = postprocess(errorsByFile[trailingText.fileName], trailingText.fileName); assert.isArray(errors, 'should return an array'); assert.lengthOf(errors, 1, 'should return one error'); assert.isString(errors[0].message, 'should have a valid message'); // we don't validate the line/column numbers since they don't actually // mean anything for this error. JSHint just bails on the file. }); it('should return multiple errors for multiple errors', function () { const errors = postprocess( errorsByFile[multipleErrors.fileName], multipleErrors.fileName ); assert.isArray(errors, 'should return an array'); assert.lengthOf(errors, 2, 'should return one error'); }); it('should return no errors for good json', function () { const errors = postprocess(errorsByFile[good.fileName], good.fileName); assert.isArray(errors, 'should return an array'); assert.lengthOf(errors, 0, "good json shouldn't have any errors"); }); }); }); ================================================ FILE: vendor/eslint-plugin-self/CHANGELOG.md ================================================ # Changelog ## v1.2.1 (2020-05-27) * Support overrides having no rules :see_no_evil: ([#5](https://github.com/not-an-aardvark/eslint-plugin-self/issues/5)) ([e266543](https://github.com/not-an-aardvark/eslint-plugin-self/commit/e266543e50d062251755dd488abae31be7644bb1)) ## v1.2.0 (2019-03-04) * Support redefining plugins, overrides and rules with a "/" in them ([#2](https://github.com/not-an-aardvark/eslint-plugin-self/issues/2)) ([428664e](https://github.com/not-an-aardvark/eslint-plugin-self/commit/428664e1cf8f3726e0bb3b10bb3e137d271749c2)) ## v1.1.0 (2018-07-06) * Chore: add release script ([983a7d0](https://github.com/not-an-aardvark/eslint-plugin-self/commit/983a7d05c48bccc125f8d89fae1109a0c5a1d670)) * Update: Add support for @scoped packages ([#1](https://github.com/not-an-aardvark/eslint-plugin-self/issues/1)) ([c57a01b](https://github.com/not-an-aardvark/eslint-plugin-self/commit/c57a01bbf922b82d09a0cceba6b5e845fab7d23a)) ## v1.0.1 (2017-07-02) * Fix: transform references to own rules in configs to use `self` prefix ([2dce85e](https://github.com/not-an-aardvark/eslint-plugin-self/commit/2dce85e445a7604f5fd963d1366509fa7a66d420)) ================================================ FILE: vendor/eslint-plugin-self/LICENSE.md ================================================ The MIT License (MIT) ===================== Copyright © 2017 Teddy Katz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: vendor/eslint-plugin-self/README.md ================================================ # eslint-plugin-self When writing an ESLint plugin, it's often useful to use the plugin's rules to lint the plugin's own codebase. You can use `eslint-plugin-self` to do that. ## Usage ``` npm install eslint-plugin-self --save-dev ``` Note: `eslint-plugin-self` must be installed locally (it will not work if installed globally), and the project that installs it must be a functioning ESLint plugin. Add the following to your config file: ```json { "plugins": [ "self" ] } ``` Then you can use your plugin's rules, with the `self/` prefix: ```json { "rules": { "self/my-custom-rule": "error" } } ``` You can also use your plugin's configs, or anything else exported by your plugin: ```json { "extends": [ "plugin:self/some-config" ] } ``` ================================================ FILE: vendor/eslint-plugin-self/index.js ================================================ 'use strict'; const plugin = require('../..'); const selfPlugin = Object.assign({}, plugin); const pkgName = require('../../package.json').name; let pluginName; if (pkgName[0] === "@") { const matches = pkgName.match(/^(@[^/]+)\/eslint-plugin(?:-(.*))?$/); pluginName = matches.slice(1, 3).filter(Boolean).join('/'); } else { pluginName = pkgName.replace(/^eslint-plugin-/, ''); } function createRuleset(rules) { return Object.keys(rules).reduce((newRules, oldRuleName) => { const newRuleName = oldRuleName.startsWith(`${pluginName}/`) ? `self${oldRuleName.slice(oldRuleName.indexOf('/'))}` : oldRuleName; newRules[newRuleName] = rules[oldRuleName]; return newRules; }, {}); } if (plugin.configs) { selfPlugin.configs = Object.assign({}, plugin.configs); Object.keys(plugin.configs).forEach(configName => { const config = plugin.configs[configName]; selfPlugin.configs[configName] = Object.assign({}, config); if (config.extends) { selfPlugin.configs[configName].extends = [].concat(config.extends) .map(extendsName => extendsName.replace(`plugin:${pluginName}/`, 'plugin:self/')); } // The Array.isArray avoids attempting to change the plugins property for // eslint v9 based configurations. if (config.plugins && Array.isArray(config.plugins)) { selfPlugin.configs[configName].plugins = [].concat(config.plugins) .map(enabledPluginName => enabledPluginName.replace(pluginName, 'self')); } if (config.rules) { selfPlugin.configs[configName].rules = createRuleset(config.rules); } if (config.overrides) { selfPlugin.configs[configName].overrides = [].concat(config.overrides) .map((override) => { if (!override.rules) return override; return Object.assign( {}, override, {rules: createRuleset(override.rules)} ); }) } }); } module.exports = selfPlugin; ================================================ FILE: vendor/eslint-plugin-self/package.json ================================================ { "name": "eslint-plugin-self", "version": "1.2.1", "description": "Allows ESLint plugins to be run on themselves", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "generate-release": "node-release-script" }, "repository": { "type": "git", "url": "git+https://github.com/not-an-aardvark/eslint-plugin-self.git" }, "keywords": [ "eslint-plugin", "eslintplugin" ], "author": "Teddy Katz", "license": "MIT", "bugs": { "url": "https://github.com/not-an-aardvark/eslint-plugin-self/issues" }, "homepage": "https://github.com/not-an-aardvark/eslint-plugin-self#readme", "devDependencies": { "@not-an-aardvark/node-release-script": "^0.1.0" } }