Skip to content

Commit

Permalink
Improve compatibility with ESLint v9 (#281)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Dec 13, 2023
1 parent 24d3669 commit c9e326e
Show file tree
Hide file tree
Showing 26 changed files with 5,170 additions and 169 deletions.
5 changes: 5 additions & 0 deletions .changeset/thick-shoes-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-jsonc": minor
---

Improve compatibility with ESLint v9
7 changes: 5 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ module.exports = {
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"no-shadow": "off",
complexity: "off",
"one-var": "off",
"no-invalid-this": "off",
// Repo rule
"no-restricted-imports": [
"error",
Expand All @@ -44,13 +47,13 @@ module.exports = {
object: "context",
property: "getSourceCode",
message:
"Please use `eslint-compat-utils` module's `getSourceCode(context).getScope()` instead.",
"Please use `eslint-compat-utils` module's `getSourceCode(context)` instead.",
},
{
object: "context",
property: "sourceCode",
message:
"Please use `eslint-compat-utils` module's `getSourceCode(context).getScope()` instead.",
"Please use `eslint-compat-utils` module's `getSourceCode(context)` instead.",
},
{
object: "context",
Expand Down
11 changes: 8 additions & 3 deletions conf/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ let ruleMap;

/** Get all rules */
module.exports = function getCoreRules() {
if (ruleMap) {
return ruleMap;
const eslint = require("eslint");
try {
return ruleMap || (ruleMap = new eslint.Linter().getRules());
} catch {
// getRules() is no longer available in flat config.
}
return (ruleMap = new (require("eslint").Linter)().getRules());

const { builtinRules } = require("eslint/use-at-your-own-risk");
return builtinRules;
};
240 changes: 231 additions & 9 deletions lib/rules/array-bracket-newline.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { createRule, defineWrapperListener, getCoreRule } from "../utils";
const coreRule = getCoreRule("array-bracket-newline");

// Most source code was copied from ESLint v8.
// MIT License. Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
import type { AST } from "jsonc-eslint-parser";
import { createRule } from "../utils";
import { getSourceCode } from "eslint-compat-utils";
import { isTokenOnSameLine } from "../utils/eslint-ast-utils";
import type { Token } from "../types";
import { isCommentToken } from "@eslint-community/eslint-utils";
type Schema0 =
| ("always" | "never" | "consistent")
| {
multiline?: boolean;
minItems?: number | null;
};
export default createRule("array-bracket-newline", {
meta: {
docs: {
Expand All @@ -10,13 +21,224 @@ export default createRule("array-bracket-newline", {
extensionRule: true,
layout: true,
},
fixable: coreRule.meta?.fixable,
hasSuggestions: (coreRule.meta as any).hasSuggestions,
schema: coreRule.meta!.schema!,
messages: coreRule.meta!.messages!,
type: coreRule.meta!.type!,
type: "layout",

fixable: "whitespace",

schema: [
{
oneOf: [
{
type: "string",
enum: ["always", "never", "consistent"],
},
{
type: "object",
properties: {
multiline: {
type: "boolean",
},
minItems: {
type: ["integer", "null"],
minimum: 0,
},
},
additionalProperties: false,
},
],
},
],

messages: {
unexpectedOpeningLinebreak: "There should be no linebreak after '['.",
unexpectedClosingLinebreak: "There should be no linebreak before ']'.",
missingOpeningLinebreak: "A linebreak is required after '['.",
missingClosingLinebreak: "A linebreak is required before ']'.",
},
},
create(context) {
return defineWrapperListener(coreRule, context, context.options);
const sourceCode = getSourceCode(context);

/**
* Normalizes a given option value.
* @param option An option value to parse.
* @returns Normalized option object.
*/
function normalizeOptionValue(option: Schema0) {
let consistent = false;
let multiline = false;
let minItems = 0;

if (option) {
if (option === "consistent") {
consistent = true;
minItems = Number.POSITIVE_INFINITY;
} else if (
option === "always" ||
(typeof option !== "string" && option.minItems === 0)
) {
minItems = 0;
} else if (option === "never") {
minItems = Number.POSITIVE_INFINITY;
} else {
multiline = Boolean(option.multiline);
minItems = option.minItems || Number.POSITIVE_INFINITY;
}
} else {
consistent = false;
multiline = true;
minItems = Number.POSITIVE_INFINITY;
}

return { consistent, multiline, minItems };
}

/**
* Normalizes a given option value.
* @param options An option value to parse.
* @returns Normalized option object.
*/
function normalizeOptions(options: Schema0) {
const value = normalizeOptionValue(options);

return { JSONArrayExpression: value, JSONArrayPattern: value };
}

/**
* Reports that there shouldn't be a linebreak after the first token
* @param node The node to report in the event of an error.
* @param token The token to use for the report.
*/
function reportNoBeginningLinebreak(node: AST.JSONNode, token: Token) {
context.report({
node: node as any,
loc: token.loc,
messageId: "unexpectedOpeningLinebreak",
fix(fixer) {
const nextToken = sourceCode.getTokenAfter(token, {
includeComments: true,
});

if (!nextToken || isCommentToken(nextToken)) return null;

return fixer.removeRange([token.range[1], nextToken.range[0]]);
},
});
}

/**
* Reports that there shouldn't be a linebreak before the last token
* @param node The node to report in the event of an error.
* @param token The token to use for the report.
*/
function reportNoEndingLinebreak(node: AST.JSONNode, token: Token) {
context.report({
node: node as any,
loc: token.loc,
messageId: "unexpectedClosingLinebreak",
fix(fixer) {
const previousToken = sourceCode.getTokenBefore(token, {
includeComments: true,
});

if (!previousToken || isCommentToken(previousToken)) return null;

return fixer.removeRange([previousToken.range[1], token.range[0]]);
},
});
}

/**
* Reports that there should be a linebreak after the first token
* @param node The node to report in the event of an error.
* @param token The token to use for the report.
*/
function reportRequiredBeginningLinebreak(
node: AST.JSONNode,
token: Token,
) {
context.report({
node: node as any,
loc: token.loc,
messageId: "missingOpeningLinebreak",
fix(fixer) {
return fixer.insertTextAfter(token, "\n");
},
});
}

/**
* Reports that there should be a linebreak before the last token
* @param node The node to report in the event of an error.
* @param token The token to use for the report.
*/
function reportRequiredEndingLinebreak(node: AST.JSONNode, token: Token) {
context.report({
node: node as any,
loc: token.loc,
messageId: "missingClosingLinebreak",
fix(fixer) {
return fixer.insertTextBefore(token, "\n");
},
});
}

/**
* Reports a given node if it violated this rule.
* @param node A node to check. This is an ArrayExpression node or an ArrayPattern node.
*/
function check(node: AST.JSONNode) {
// @ts-expect-error type cast
const elements = node.elements;
const normalizedOptions = normalizeOptions(context.options[0]);
// @ts-expect-error type cast
const options = normalizedOptions[node.type];
const openBracket = sourceCode.getFirstToken(node as any)!;
const closeBracket = sourceCode.getLastToken(node as any)!;
const firstIncComment = sourceCode.getTokenAfter(openBracket, {
includeComments: true,
})!;
const lastIncComment = sourceCode.getTokenBefore(closeBracket, {
includeComments: true,
})!;
const first = sourceCode.getTokenAfter(openBracket)!;
const last = sourceCode.getTokenBefore(closeBracket)!;
const needsLinebreaks =
elements.length >= options.minItems ||
(options.multiline &&
elements.length > 0 &&
firstIncComment.loc!.start.line !== lastIncComment.loc!.end.line) ||
(elements.length === 0 &&
firstIncComment.type === "Block" &&
firstIncComment.loc!.start.line !== lastIncComment.loc!.end.line &&
firstIncComment === lastIncComment) ||
(options.consistent &&
openBracket.loc.end.line !== first.loc.start.line);

/**
* Use tokens or comments to check multiline or not.
* But use only tokens to check whether linebreaks are needed.
* This allows:
* var arr = [ // eslint-disable-line foo
* 'a'
* ]
*/

if (needsLinebreaks) {
if (isTokenOnSameLine(openBracket, first))
reportRequiredBeginningLinebreak(node, openBracket);
if (isTokenOnSameLine(last, closeBracket))
reportRequiredEndingLinebreak(node, closeBracket);
} else {
if (!isTokenOnSameLine(openBracket, first))
reportNoBeginningLinebreak(node, openBracket);
if (!isTokenOnSameLine(last, closeBracket))
reportNoEndingLinebreak(node, closeBracket);
}
}

return {
JSONArrayExpression: check,
};
},
});

0 comments on commit c9e326e

Please sign in to comment.