diff --git a/src/rules/no-duplicate-keys.js b/src/rules/no-duplicate-keys.js index e3ac3d6..6897acb 100644 --- a/src/rules/no-duplicate-keys.js +++ b/src/rules/no-duplicate-keys.js @@ -16,7 +16,7 @@ //----------------------------------------------------------------------------- /** @type {NoDuplicateKeysRuleDefinition} */ -export default { +const rule = { meta: { type: "problem", @@ -66,3 +66,5 @@ export default { }; }, }; + +export default rule; diff --git a/src/rules/no-empty-keys.js b/src/rules/no-empty-keys.js index 73983cc..a85aa91 100644 --- a/src/rules/no-empty-keys.js +++ b/src/rules/no-empty-keys.js @@ -15,7 +15,7 @@ //----------------------------------------------------------------------------- /** @type {NoEmptyKeysRuleDefinition} */ -export default { +const rule = { meta: { type: "problem", @@ -46,3 +46,5 @@ export default { }; }, }; + +export default rule; diff --git a/src/rules/no-unnormalized-keys.js b/src/rules/no-unnormalized-keys.js index 716959f..b5a52bb 100644 --- a/src/rules/no-unnormalized-keys.js +++ b/src/rules/no-unnormalized-keys.js @@ -3,9 +3,21 @@ * @author Bradley Meck Farias */ -export default { +//----------------------------------------------------------------------------- +// Type Definitions +//----------------------------------------------------------------------------- + +/** @typedef {"unnormalizedKey"} NoUnnormalizedKeysMessageIds */ +/** @typedef {import("../types.ts").JSONRuleDefinition<[{form:string}], NoUnnormalizedKeysMessageIds>} NoUnnormalizedKeysRuleDefinition */ + +//----------------------------------------------------------------------------- +// Rule Definition +//----------------------------------------------------------------------------- + +/** @type {NoUnnormalizedKeysRuleDefinition} */ +const rule = { meta: { - type: /** @type {const} */ ("problem"), + type: "problem", docs: { description: "Disallow JSON keys that are not normalized", @@ -29,9 +41,10 @@ export default { }, create(context) { - const normalization = context.options.length - ? text => text.normalize(context.options[0].form) - : text => text.normalize(); + const form = context.options.length + ? context.options[0].form + : undefined; + return { Member(node) { const key = @@ -39,7 +52,7 @@ export default { ? node.name.value : node.name.name; - if (normalization(key) !== key) { + if (key.normalize(form) !== key) { context.report({ loc: node.name.loc, messageId: "unnormalizedKey", @@ -52,3 +65,5 @@ export default { }; }, }; + +export default rule; diff --git a/src/rules/no-unsafe-values.js b/src/rules/no-unsafe-values.js index 6249e23..63bc519 100644 --- a/src/rules/no-unsafe-values.js +++ b/src/rules/no-unsafe-values.js @@ -30,7 +30,7 @@ const NON_ZERO = /[1-9]/u; //----------------------------------------------------------------------------- /** @type {NoUnsafeValuesRuleDefinition} */ -export default { +const rule = { meta: { type: "problem", @@ -145,3 +145,5 @@ export default { }; }, }; + +export default rule; diff --git a/src/rules/sort-keys.js b/src/rules/sort-keys.js index d779d63..3d6cfc7 100644 --- a/src/rules/sort-keys.js +++ b/src/rules/sort-keys.js @@ -1,48 +1,99 @@ /** - * @fileoverview Rule to require JSON object keys to be sorted. Copied largely from https://github.com/eslint/eslint/blob/main/lib/rules/sort-keys.js + * @fileoverview Rule to require JSON object keys to be sorted. + * Copied largely from https://github.com/eslint/eslint/blob/main/lib/rules/sort-keys.js * @author Robin Thomas */ +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + import naturalCompare from "natural-compare"; +//----------------------------------------------------------------------------- +// Type Definitions +//----------------------------------------------------------------------------- + +/** @typedef {"sortKeys"} SortKeysMessageIds */ + +/** + * @typedef {Object} SortOptions + * @property {boolean} caseSensitive + * @property {boolean} natural + * @property {number} minKeys + * @property {boolean} allowLineSeparatedGroups + */ + +/** @typedef {"asc"|"desc"} SortDirection */ +/** @typedef {[SortDirection, SortOptions]} SortKeysRuleOptions */ +/** @typedef {import("../types.ts").JSONRuleDefinition} SortKeysRuleDefinition */ +/** @typedef {(a:string,b:string) => boolean} Comparator */ +/** @typedef {import("@humanwhocodes/momoa").MemberNode} MemberNode */ + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + const hasNonWhitespace = /\S/u; 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), }, }, }; +/** + * Gets the MemberNode's string key value. + * @param {MemberNode} member + * @return {string} + */ function getKey(member) { - return member.name.type === `Identifier` + return member.name.type === "Identifier" ? member.name.name : member.name.value; } -export default { +//----------------------------------------------------------------------------- +// Rule Definition +//----------------------------------------------------------------------------- + +/** @type {SortKeysRuleDefinition} */ +const rule = { meta: { - type: /** @type {const} */ ("suggestion"), + type: "suggestion", defaultOptions: [ "asc", @@ -112,8 +163,14 @@ export default { } } - // Note that there can be comments *inside* members, e.g. `{"foo: /* comment *\/ "bar"}`, but these are ignored when calculating line-separated groups + /** + * Checks if two members are line-separated. + * @param {MemberNode} prevMember The previous member. + * @param {MemberNode} member The current member. + * @return {boolean} + */ 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 const prevMemberEndLine = prevMember.loc.end.line; const thisStartLine = member.loc.start.line; if (thisStartLine - prevMemberEndLine < 2) { @@ -176,3 +233,5 @@ export default { }; }, }; + +export default rule; diff --git a/src/rules/top-level-interop.js b/src/rules/top-level-interop.js index ea4d7fa..96fb232 100644 --- a/src/rules/top-level-interop.js +++ b/src/rules/top-level-interop.js @@ -3,9 +3,21 @@ * @author Joe Hildebrand */ -export default { +//----------------------------------------------------------------------------- +// Type Definitions +//----------------------------------------------------------------------------- + +/** @typedef {"topLevel"} TopLevelInteropMessageIds */ +/** @typedef {import("../types.ts").JSONRuleDefinition<[], TopLevelInteropMessageIds>} TopLevelInteropRuleDefinition */ + +//----------------------------------------------------------------------------- +// Rule Definition +//----------------------------------------------------------------------------- + +/** @type {TopLevelInteropRuleDefinition} */ +const rule = { meta: { - type: /** @type {const} */ ("problem"), + type: "problem", docs: { description: @@ -33,3 +45,5 @@ export default { }; }, }; + +export default rule; diff --git a/src/types.ts b/src/types.ts index 9c08e8b..f8dc7e1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -105,6 +105,16 @@ export interface IJSONSourceCode beforeCount?: number, afterCount?: number, ): string; + + /** + * Any comments found in a JSONC or JSON5 file. + */ + comments: Array | undefined; + + /** + * The lines of text found in the file. + */ + lines: Array; } export type IJSONLanguage = Language<{