Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 22 additions & 43 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,14 @@

import eslintConfigESLint from "eslint-config-eslint";
import eslintPlugin from "eslint-plugin-eslint-plugin";
import globals from "globals";
import json from "./src/index.js";
import { defineConfig, globalIgnores } from "eslint/config";

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

const eslintPluginJSDoc = eslintConfigESLint.find(
config => config.plugins?.jsdoc,
).plugins.jsdoc;

const eslintPluginRulesRecommendedConfig =
eslintPlugin.configs["flat/rules-recommended"];
const eslintPluginTestsRecommendedConfig =
Expand All @@ -30,54 +27,34 @@ const eslintPluginTestsRecommendedConfig =
//-----------------------------------------------------------------------------

export default defineConfig([
globalIgnores([
"**/tests/fixtures/",
"**/dist/",
"coverage/",
"src/build/",
]),

...eslintConfigESLint.map(config => ({
files: ["**/*.js"],
...config,
})),
{
plugins: { json },
files: ["**/*.json", ".c8rc"],
language: "json/json",
extends: ["json/recommended"],
},
globalIgnores(["coverage/", "dist/", "src/build/"], "json/global-ignores"),
{
name: "json/js",
files: ["**/*.js"],
extends: [eslintConfigESLint],
rules: {
// disable rules we don't want to use from eslint-config-eslint
"no-undefined": "off",

// TODO: re-enable eslint-plugin-jsdoc rules
...Object.fromEntries(
Object.keys(eslintPluginJSDoc.rules).map(name => [
`jsdoc/${name}`,
"off",
]),
),
},
},
{
files: ["**/tests/**"],
name: "json/tools",
files: ["tools/**/*.js"],
rules: {
"no-console": "off",
},
},
{
name: "json/tests",
files: ["tests/**/*.js"],
ignores: ["tests/rules/*.js"],
languageOptions: {
globals: {
describe: "readonly",
xdescribe: "readonly",
it: "readonly",
xit: "readonly",
beforeEach: "readonly",
afterEach: "readonly",
before: "readonly",
after: "readonly",
...globals.mocha,
},
},
},
{
name: "json/rules",
files: ["src/rules/*.js"],
extends: [eslintPluginRulesRecommendedConfig],
rules: {
Expand All @@ -99,6 +76,7 @@ export default defineConfig([
},
},
{
name: "json/rules-tests",
files: ["tests/rules/*.test.js"],
extends: [eslintPluginTestsRecommendedConfig],
rules: {
Expand All @@ -119,9 +97,10 @@ export default defineConfig([
},
},
{
files: ["tools/**/*.js"],
rules: {
"no-console": "off",
},
name: "json/json",
plugins: { json },
files: ["**/*.json", ".c8rc"],
language: "json/json",
extends: ["json/recommended"],
},
]);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"eslint": "^9.36.0",
"eslint-config-eslint": "^13.0.0",
"eslint-plugin-eslint-plugin": "^6.3.2",
"globals": "^16.5.0",
"lint-staged": "^15.2.7",
"mdast-util-from-markdown": "^2.0.2",
"mocha": "^11.3.0",
Expand Down
2 changes: 0 additions & 2 deletions src/languages/json-language.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ import { visitorKeys } from "@humanwhocodes/momoa";
/**
* @import { DocumentNode, AnyNode } from "@humanwhocodes/momoa";
* @import { Language, OkParseResult, ParseResult, File } from "@eslint/core";
*
* @typedef {OkParseResult<DocumentNode>} JSONOkParseResult
* @typedef {ParseResult<DocumentNode>} JSONParseResult
*
* @typedef {Object} JSONLanguageOptions
* @property {boolean} [allowTrailingCommas] Whether to allow trailing commas in JSONC mode.
*/
Expand Down
1 change: 0 additions & 1 deletion src/rules/no-duplicate-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { getKey, getRawKey } from "../util.js";
/**
* @import { MemberNode } from "@humanwhocodes/momoa";
* @import { JSONRuleDefinition } from "../types.ts";
*
* @typedef {"duplicateKey"} NoDuplicateKeysMessageIds
* @typedef {JSONRuleDefinition<{ MessageIds: NoDuplicateKeysMessageIds }>} NoDuplicateKeysRuleDefinition
*/
Expand Down
1 change: 0 additions & 1 deletion src/rules/no-empty-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { getKey } from "../util.js";

/**
* @import { JSONRuleDefinition } from "../types.ts";
*
* @typedef {"emptyKey"} NoEmptyKeysMessageIds
* @typedef {JSONRuleDefinition<{ MessageIds: NoEmptyKeysMessageIds }>} NoEmptyKeysRuleDefinition
*/
Expand Down
1 change: 0 additions & 1 deletion src/rules/no-unnormalized-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { getKey } from "../util.js";

/**
* @import { JSONRuleDefinition } from "../types.ts";
*
* @typedef {"unnormalizedKey"} NoUnnormalizedKeysMessageIds
* @typedef {{ form: string }} NoUnnormalizedKeysOptions
* @typedef {JSONRuleDefinition<{ RuleOptions: [NoUnnormalizedKeysOptions], MessageIds: NoUnnormalizedKeysMessageIds }>} NoUnnormalizedKeysRuleDefinition
Expand Down
1 change: 0 additions & 1 deletion src/rules/no-unsafe-values.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

/**
* @import { JSONRuleDefinition } from "../types.ts";
*
* @typedef {"unsafeNumber"|"unsafeInteger"|"unsafeZero"|"subnormal"|"loneSurrogate"} NoUnsafeValuesMessageIds
* @typedef {JSONRuleDefinition<{ MessageIds: NoUnsafeValuesMessageIds }>} NoUnsafeValuesRuleDefinition
*/
Expand Down
31 changes: 14 additions & 17 deletions src/rules/sort-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@ import { getKey, getRawKey } from "../util.js";
/**
* @import { JSONRuleDefinition } from "../types.ts";
* @import { MemberNode } from "@humanwhocodes/momoa";
*
* @typedef {Object} SortOptions
* @property {boolean} caseSensitive
* @property {boolean} natural
* @property {number} minKeys
* @property {boolean} allowLineSeparatedGroups
*
* @property {boolean} caseSensitive Whether key comparisons are case-sensitive.
* @property {boolean} natural Whether to use natural sort order instead of purely alphanumeric.
* @property {number} minKeys Minimum number of keys in an object before enforcing sorting.
* @property {boolean} allowLineSeparatedGroups Whether a blank line between properties starts a new group that is independently sorted.
* @typedef {"sortKeys"} SortKeysMessageIds
* @typedef {"asc"|"desc"} SortDirection
* @typedef {[SortDirection, SortOptions]} SortKeysRuleOptions
* @typedef {JSONRuleDefinition<{ RuleOptions: SortKeysRuleOptions, MessageIds: SortKeysMessageIds }>} SortKeysRuleDefinition
* @typedef {(a:string,b:string) => boolean} Comparator
* @typedef {"ascending"|"descending"} DirectionName
* @typedef {"alphanumeric"|"natural"} SortName
* @typedef {"sensitive"|"insensitive"} Sensitivity
* @typedef {Record<DirectionName, Record<SortName, Record<Sensitivity, Comparator>>>} ComparatorMap
*/

//-----------------------------------------------------------------------------
Expand All @@ -38,39 +40,30 @@ import { getKey, getRawKey } from "../util.js";

const hasNonWhitespace = /\S/u;

/** @type {ComparatorMap} */
const comparators = {
ascending: {
alphanumeric: {
/** @type {Comparator} */
sensitive: (a, b) => a <= b,

/** @type {Comparator} */
insensitive: (a, b) => a.toLowerCase() <= b.toLowerCase(),
},
natural: {
/** @type {Comparator} */
sensitive: (a, b) => naturalCompare(a, b) <= 0,

/** @type {Comparator} */
insensitive: (a, b) =>
naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0,
},
},
descending: {
alphanumeric: {
/** @type {Comparator} */
sensitive: (a, b) =>
comparators.ascending.alphanumeric.sensitive(b, a),

/** @type {Comparator} */
insensitive: (a, b) =>
comparators.ascending.alphanumeric.insensitive(b, a),
},
natural: {
/** @type {Comparator} */
sensitive: (a, b) => comparators.ascending.natural.sensitive(b, a),

/** @type {Comparator} */
insensitive: (a, b) =>
comparators.ascending.natural.insensitive(b, a),
},
Expand Down Expand Up @@ -140,9 +133,13 @@ const rule = {
{ allowLineSeparatedGroups, caseSensitive, natural, minKeys },
] = context.options;

/** @type {DirectionName} */
const direction = directionShort === "asc" ? "ascending" : "descending";
/** @type {SortName} */
const sortName = natural ? "natural" : "alphanumeric";
/** @type {Sensitivity} */
const sensitivity = caseSensitive ? "sensitive" : "insensitive";
/** @type {Comparator} */
const isValidOrder = comparators[direction][sortName][sensitivity];

// Note that @humanwhocodes/momoa doesn't include comments in the object.members tree, so we can't just see if a member is preceded by a comment
Expand All @@ -161,7 +158,7 @@ const rule = {
* Checks if two members are line-separated.
* @param {MemberNode} prevMember The previous member.
* @param {MemberNode} member The current member.
* @return {boolean}
* @returns {boolean} True if the members are separated by at least one blank line (ignoring comment-only lines).
*/
function isLineSeparated(prevMember, member) {
// Note that there can be comments *inside* members, e.g. `{"foo: /* comment *\/ "bar"}`, but these are ignored when calculating line-separated groups
Expand Down
1 change: 0 additions & 1 deletion src/rules/top-level-interop.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

/**
* @import { JSONRuleDefinition } from "../types.ts";
*
* @typedef {"topLevel"} TopLevelInteropMessageIds
* @typedef {JSONRuleDefinition<{ MessageIds: TopLevelInteropMessageIds }>} TopLevelInteropRuleDefinition
*/
Expand Down