| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- 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 makeRule = (errorName, reporters) => ({
- 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 processors = {
- '.json': {
- 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 configs = {
- recommended: {
- plugins: ['json'],
- rules: {
- 'json/*': 'error',
- },
- },
- 'recommended-with-comments': {
- plugins: ['json'],
- rules: {
- 'json/*': ['error', {allowComments: true}],
- },
- },
- };
- module.exports = {rules, configs, processors};
|