@eslint/json 0.14.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/build/recommended-config.d.ts +7 -0
- package/dist/build/recommended-config.js +7 -0
- package/dist/build/rules.d.ts +9 -0
- package/dist/build/rules.js +14 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +41 -0
- package/dist/languages/json-language.d.ts +85 -0
- package/dist/languages/json-language.js +143 -0
- package/dist/languages/json-source-code.d.ts +123 -0
- package/dist/languages/json-source-code.js +327 -0
- package/dist/rules/no-duplicate-keys.d.ts +14 -0
- package/dist/rules/no-duplicate-keys.js +66 -0
- package/dist/rules/no-empty-keys.d.ts +13 -0
- package/dist/rules/no-empty-keys.js +47 -0
- package/dist/rules/no-unnormalized-keys.d.ts +18 -0
- package/dist/rules/no-unnormalized-keys.js +68 -0
- package/dist/rules/no-unsafe-values.d.ts +8 -0
- package/dist/rules/no-unsafe-values.js +139 -0
- package/dist/rules/sort-keys.d.ts +34 -0
- package/dist/rules/sort-keys.js +185 -0
- package/dist/rules/top-level-interop.d.ts +17 -0
- package/dist/rules/top-level-interop.js +44 -0
- package/dist/types.js +5 -0
- package/dist/util.d.ts +23 -0
- package/dist/util.js +33 -0
- package/package.json +18 -36
- package/dist/cjs/index.cjs +0 -1271
- package/dist/cjs/index.d.cts +0 -280
- package/dist/cjs/types.cts +0 -87
- package/dist/esm/index.d.ts +0 -280
- package/dist/esm/index.js +0 -1266
- package/dist/esm/types.ts +0 -87
- /package/dist/{esm/types.d.ts → types.d.ts} +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule to detect unsafe values in JSON.
|
|
3
|
+
* @author Bradley Meck Farias
|
|
4
|
+
*/
|
|
5
|
+
//-----------------------------------------------------------------------------
|
|
6
|
+
// Type Definitions
|
|
7
|
+
//-----------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* @import { JSONRuleDefinition } from "../types.js";
|
|
10
|
+
* @typedef {"unsafeNumber"|"unsafeInteger"|"unsafeZero"|"subnormal"|"loneSurrogate"} NoUnsafeValuesMessageIds
|
|
11
|
+
* @typedef {JSONRuleDefinition<{ MessageIds: NoUnsafeValuesMessageIds }>} NoUnsafeValuesRuleDefinition
|
|
12
|
+
*/
|
|
13
|
+
//-----------------------------------------------------------------------------
|
|
14
|
+
// Helpers
|
|
15
|
+
//-----------------------------------------------------------------------------
|
|
16
|
+
/*
|
|
17
|
+
* This rule is based on the JSON grammar from RFC 8259, section 6.
|
|
18
|
+
* https://tools.ietf.org/html/rfc8259#section-6
|
|
19
|
+
*
|
|
20
|
+
* Also, this rule is based on the JSON5 grammar from json5.org, section 6.
|
|
21
|
+
* https://spec.json5.org/#numbers
|
|
22
|
+
*
|
|
23
|
+
* We separately capture the integer and fractional parts of a number, so that
|
|
24
|
+
* we can check for unsafe numbers that will evaluate to Infinity.
|
|
25
|
+
*/
|
|
26
|
+
const NUMBER = /^[+-]?(?<int>0|([1-9]\d*))?(?:\.(?<frac>\d*))?(?:e[+-]?\d+)?$/iu;
|
|
27
|
+
const NON_ZERO = /[1-9]/u;
|
|
28
|
+
//-----------------------------------------------------------------------------
|
|
29
|
+
// Rule Definition
|
|
30
|
+
//-----------------------------------------------------------------------------
|
|
31
|
+
/** @type {NoUnsafeValuesRuleDefinition} */
|
|
32
|
+
const rule = {
|
|
33
|
+
meta: {
|
|
34
|
+
type: "problem",
|
|
35
|
+
docs: {
|
|
36
|
+
recommended: true,
|
|
37
|
+
description: "Disallow JSON values that are unsafe for interchange",
|
|
38
|
+
url: "https://github.com/eslint/json/tree/main/docs/rules/no-unsafe-values.md",
|
|
39
|
+
},
|
|
40
|
+
messages: {
|
|
41
|
+
unsafeNumber: "The number '{{ value }}' will evaluate to Infinity.",
|
|
42
|
+
unsafeInteger: "The integer '{{ value }}' is outside the safe integer range.",
|
|
43
|
+
unsafeZero: "The number '{{ value }}' will evaluate to zero.",
|
|
44
|
+
subnormal: "Unexpected subnormal number '{{ value }}' found, which may cause interoperability issues.",
|
|
45
|
+
loneSurrogate: "Lone surrogate '{{ surrogate }}' found.",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
create(context) {
|
|
49
|
+
return {
|
|
50
|
+
Number(node) {
|
|
51
|
+
const value = context.sourceCode.getText(node);
|
|
52
|
+
if (Number.isFinite(node.value) !== true) {
|
|
53
|
+
context.report({
|
|
54
|
+
loc: node.loc,
|
|
55
|
+
messageId: "unsafeNumber",
|
|
56
|
+
data: { value },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Also matches -0, intentionally
|
|
61
|
+
if (node.value === 0) {
|
|
62
|
+
// If the value has been rounded down to 0, but there was some
|
|
63
|
+
// fraction or non-zero part before the e-, this is a very small
|
|
64
|
+
// number that doesn't fit inside an f64.
|
|
65
|
+
const match = value.match(NUMBER);
|
|
66
|
+
if (match === null) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// If any part of the number other than the exponent has a
|
|
70
|
+
// non-zero digit in it, this number was not intended to be
|
|
71
|
+
// evaluated down to a zero.
|
|
72
|
+
if (NON_ZERO.test(match.groups.int) ||
|
|
73
|
+
NON_ZERO.test(match.groups.frac)) {
|
|
74
|
+
context.report({
|
|
75
|
+
loc: node.loc,
|
|
76
|
+
messageId: "unsafeZero",
|
|
77
|
+
data: { value },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (!/[.e]/iu.test(value)) {
|
|
82
|
+
// Intended to be an integer
|
|
83
|
+
if (node.value > Number.MAX_SAFE_INTEGER ||
|
|
84
|
+
node.value < Number.MIN_SAFE_INTEGER) {
|
|
85
|
+
context.report({
|
|
86
|
+
loc: node.loc,
|
|
87
|
+
messageId: "unsafeInteger",
|
|
88
|
+
data: { value },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Floating point. Check for subnormal.
|
|
94
|
+
const buffer = new ArrayBuffer(8);
|
|
95
|
+
const view = new DataView(buffer);
|
|
96
|
+
view.setFloat64(0, node.value, false);
|
|
97
|
+
const asBigInt = view.getBigUint64(0, false);
|
|
98
|
+
// Subnormals have an 11-bit exponent of 0 and a non-zero mantissa.
|
|
99
|
+
if ((asBigInt & 0x7ff0000000000000n) === 0n) {
|
|
100
|
+
context.report({
|
|
101
|
+
loc: node.loc,
|
|
102
|
+
messageId: "subnormal",
|
|
103
|
+
// Value included so that it's seen in scientific notation
|
|
104
|
+
data: {
|
|
105
|
+
value,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
String(node) {
|
|
113
|
+
if (node.value.isWellFormed) {
|
|
114
|
+
if (node.value.isWellFormed()) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// match any high surrogate and, if it exists, a paired low surrogate
|
|
119
|
+
// match any low surrogate not already matched
|
|
120
|
+
const surrogatePattern = /[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/gu;
|
|
121
|
+
/** @type {RegExpExecArray | null} */
|
|
122
|
+
let match;
|
|
123
|
+
while ((match = surrogatePattern.exec(node.value)) !== null) {
|
|
124
|
+
// only need to report non-paired surrogates
|
|
125
|
+
if (match[0].length < 2) {
|
|
126
|
+
context.report({
|
|
127
|
+
loc: node.loc,
|
|
128
|
+
messageId: "loneSurrogate",
|
|
129
|
+
data: {
|
|
130
|
+
surrogate: JSON.stringify(match[0]).slice(1, -1),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
export default rule;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default rule;
|
|
2
|
+
export type SortOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Whether key comparisons are case-sensitive.
|
|
5
|
+
*/
|
|
6
|
+
caseSensitive: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Whether to use natural sort order instead of purely alphanumeric.
|
|
9
|
+
*/
|
|
10
|
+
natural: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Minimum number of keys in an object before enforcing sorting.
|
|
13
|
+
*/
|
|
14
|
+
minKeys: number;
|
|
15
|
+
/**
|
|
16
|
+
* Whether a blank line between properties starts a new group that is independently sorted.
|
|
17
|
+
*/
|
|
18
|
+
allowLineSeparatedGroups: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type SortKeysMessageIds = "sortKeys";
|
|
21
|
+
export type SortDirection = "asc" | "desc";
|
|
22
|
+
export type SortKeysRuleOptions = [SortDirection, SortOptions];
|
|
23
|
+
export type SortKeysRuleDefinition = JSONRuleDefinition<{
|
|
24
|
+
RuleOptions: SortKeysRuleOptions;
|
|
25
|
+
MessageIds: SortKeysMessageIds;
|
|
26
|
+
}>;
|
|
27
|
+
export type Comparator = (a: string, b: string) => boolean;
|
|
28
|
+
export type DirectionName = "ascending" | "descending";
|
|
29
|
+
export type SortName = "alphanumeric" | "natural";
|
|
30
|
+
export type Sensitivity = "sensitive" | "insensitive";
|
|
31
|
+
export type ComparatorMap = Record<DirectionName, Record<SortName, Record<Sensitivity, Comparator>>>;
|
|
32
|
+
/** @type {SortKeysRuleDefinition} */
|
|
33
|
+
declare const rule: SortKeysRuleDefinition;
|
|
34
|
+
import type { JSONRuleDefinition } from "../types.js";
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule to require JSON object keys to be sorted.
|
|
3
|
+
* Copied largely from https://github.com/eslint/eslint/blob/main/lib/rules/sort-keys.js
|
|
4
|
+
* @author Robin Thomas
|
|
5
|
+
*/
|
|
6
|
+
//-----------------------------------------------------------------------------
|
|
7
|
+
// Imports
|
|
8
|
+
//-----------------------------------------------------------------------------
|
|
9
|
+
import naturalCompare from "natural-compare";
|
|
10
|
+
import { getKey, getRawKey } from "../util.js";
|
|
11
|
+
//-----------------------------------------------------------------------------
|
|
12
|
+
// Type Definitions
|
|
13
|
+
//-----------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* @import { JSONRuleDefinition } from "../types.js";
|
|
16
|
+
* @import { MemberNode } from "@humanwhocodes/momoa";
|
|
17
|
+
* @typedef {Object} SortOptions
|
|
18
|
+
* @property {boolean} caseSensitive Whether key comparisons are case-sensitive.
|
|
19
|
+
* @property {boolean} natural Whether to use natural sort order instead of purely alphanumeric.
|
|
20
|
+
* @property {number} minKeys Minimum number of keys in an object before enforcing sorting.
|
|
21
|
+
* @property {boolean} allowLineSeparatedGroups Whether a blank line between properties starts a new group that is independently sorted.
|
|
22
|
+
* @typedef {"sortKeys"} SortKeysMessageIds
|
|
23
|
+
* @typedef {"asc"|"desc"} SortDirection
|
|
24
|
+
* @typedef {[SortDirection, SortOptions]} SortKeysRuleOptions
|
|
25
|
+
* @typedef {JSONRuleDefinition<{ RuleOptions: SortKeysRuleOptions, MessageIds: SortKeysMessageIds }>} SortKeysRuleDefinition
|
|
26
|
+
* @typedef {(a:string,b:string) => boolean} Comparator
|
|
27
|
+
* @typedef {"ascending"|"descending"} DirectionName
|
|
28
|
+
* @typedef {"alphanumeric"|"natural"} SortName
|
|
29
|
+
* @typedef {"sensitive"|"insensitive"} Sensitivity
|
|
30
|
+
* @typedef {Record<DirectionName, Record<SortName, Record<Sensitivity, Comparator>>>} ComparatorMap
|
|
31
|
+
*/
|
|
32
|
+
//-----------------------------------------------------------------------------
|
|
33
|
+
// Helpers
|
|
34
|
+
//-----------------------------------------------------------------------------
|
|
35
|
+
const hasNonWhitespace = /\S/u;
|
|
36
|
+
/** @type {ComparatorMap} */
|
|
37
|
+
const comparators = {
|
|
38
|
+
ascending: {
|
|
39
|
+
alphanumeric: {
|
|
40
|
+
sensitive: (a, b) => a <= b,
|
|
41
|
+
insensitive: (a, b) => a.toLowerCase() <= b.toLowerCase(),
|
|
42
|
+
},
|
|
43
|
+
natural: {
|
|
44
|
+
sensitive: (a, b) => naturalCompare(a, b) <= 0,
|
|
45
|
+
insensitive: (a, b) => naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
descending: {
|
|
49
|
+
alphanumeric: {
|
|
50
|
+
sensitive: (a, b) => comparators.ascending.alphanumeric.sensitive(b, a),
|
|
51
|
+
insensitive: (a, b) => comparators.ascending.alphanumeric.insensitive(b, a),
|
|
52
|
+
},
|
|
53
|
+
natural: {
|
|
54
|
+
sensitive: (a, b) => comparators.ascending.natural.sensitive(b, a),
|
|
55
|
+
insensitive: (a, b) => comparators.ascending.natural.insensitive(b, a),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
//-----------------------------------------------------------------------------
|
|
60
|
+
// Rule Definition
|
|
61
|
+
//-----------------------------------------------------------------------------
|
|
62
|
+
/** @type {SortKeysRuleDefinition} */
|
|
63
|
+
const rule = {
|
|
64
|
+
meta: {
|
|
65
|
+
type: "suggestion",
|
|
66
|
+
defaultOptions: [
|
|
67
|
+
"asc",
|
|
68
|
+
{
|
|
69
|
+
allowLineSeparatedGroups: false,
|
|
70
|
+
caseSensitive: true,
|
|
71
|
+
minKeys: 2,
|
|
72
|
+
natural: false,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
docs: {
|
|
76
|
+
recommended: false,
|
|
77
|
+
description: `Require JSON object keys to be sorted`,
|
|
78
|
+
url: "https://github.com/eslint/json/tree/main/docs/rules/sort-keys.md",
|
|
79
|
+
},
|
|
80
|
+
messages: {
|
|
81
|
+
sortKeys: "Expected object keys to be in {{sortName}} case-{{sensitivity}} {{direction}} order. '{{thisName}}' should be before '{{prevName}}'.",
|
|
82
|
+
},
|
|
83
|
+
schema: [
|
|
84
|
+
{
|
|
85
|
+
enum: ["asc", "desc"],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
caseSensitive: {
|
|
91
|
+
type: "boolean",
|
|
92
|
+
},
|
|
93
|
+
natural: {
|
|
94
|
+
type: "boolean",
|
|
95
|
+
},
|
|
96
|
+
minKeys: {
|
|
97
|
+
type: "integer",
|
|
98
|
+
minimum: 2,
|
|
99
|
+
},
|
|
100
|
+
allowLineSeparatedGroups: {
|
|
101
|
+
type: "boolean",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
create(context) {
|
|
109
|
+
const { sourceCode } = context;
|
|
110
|
+
const [directionShort, { allowLineSeparatedGroups, caseSensitive, natural, minKeys },] = context.options;
|
|
111
|
+
/** @type {DirectionName} */
|
|
112
|
+
const direction = directionShort === "asc" ? "ascending" : "descending";
|
|
113
|
+
/** @type {SortName} */
|
|
114
|
+
const sortName = natural ? "natural" : "alphanumeric";
|
|
115
|
+
/** @type {Sensitivity} */
|
|
116
|
+
const sensitivity = caseSensitive ? "sensitive" : "insensitive";
|
|
117
|
+
/** @type {Comparator} */
|
|
118
|
+
const isValidOrder = comparators[direction][sortName][sensitivity];
|
|
119
|
+
// 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
|
|
120
|
+
const commentLineNums = new Set();
|
|
121
|
+
for (const comment of sourceCode.comments) {
|
|
122
|
+
for (let lineNum = comment.loc.start.line; lineNum <= comment.loc.end.line; lineNum += 1) {
|
|
123
|
+
commentLineNums.add(lineNum);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Checks if two members are line-separated.
|
|
128
|
+
* @param {MemberNode} prevMember The previous member.
|
|
129
|
+
* @param {MemberNode} member The current member.
|
|
130
|
+
* @returns {boolean} True if the members are separated by at least one blank line (ignoring comment-only lines).
|
|
131
|
+
*/
|
|
132
|
+
function isLineSeparated(prevMember, member) {
|
|
133
|
+
// Note that there can be comments *inside* members, e.g. `{"foo: /* comment *\/ "bar"}`, but these are ignored when calculating line-separated groups
|
|
134
|
+
const prevMemberEndLine = prevMember.loc.end.line;
|
|
135
|
+
const thisStartLine = member.loc.start.line;
|
|
136
|
+
if (thisStartLine - prevMemberEndLine < 2) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
for (let lineNum = prevMemberEndLine + 1; lineNum < thisStartLine; lineNum += 1) {
|
|
140
|
+
if (!commentLineNums.has(lineNum) &&
|
|
141
|
+
!hasNonWhitespace.test(sourceCode.lines[lineNum - 1])) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
Object(node) {
|
|
149
|
+
/** @type {MemberNode} */
|
|
150
|
+
let prevMember;
|
|
151
|
+
/** @type {string} */
|
|
152
|
+
let prevName;
|
|
153
|
+
/** @type {string} */
|
|
154
|
+
let prevRawName;
|
|
155
|
+
if (node.members.length < minKeys) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const member of node.members) {
|
|
159
|
+
const thisName = getKey(member);
|
|
160
|
+
const thisRawName = getRawKey(member, sourceCode);
|
|
161
|
+
if (prevMember &&
|
|
162
|
+
!isValidOrder(prevName, thisName) &&
|
|
163
|
+
(!allowLineSeparatedGroups ||
|
|
164
|
+
!isLineSeparated(prevMember, member))) {
|
|
165
|
+
context.report({
|
|
166
|
+
loc: member.name.loc,
|
|
167
|
+
messageId: "sortKeys",
|
|
168
|
+
data: {
|
|
169
|
+
thisName: thisRawName,
|
|
170
|
+
prevName: prevRawName,
|
|
171
|
+
direction,
|
|
172
|
+
sensitivity,
|
|
173
|
+
sortName,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
prevMember = member;
|
|
178
|
+
prevName = thisName;
|
|
179
|
+
prevRawName = thisRawName;
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
export default rule;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default rule;
|
|
2
|
+
export type TopLevelInteropMessageIds = "topLevel";
|
|
3
|
+
export type TopLevelInteropRuleDefinition = JSONRuleDefinition<{
|
|
4
|
+
MessageIds: TopLevelInteropMessageIds;
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* @fileoverview Rule to ensure top-level items are either an array or object.
|
|
8
|
+
* @author Joe Hildebrand
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* @import { JSONRuleDefinition } from "../types.js";
|
|
12
|
+
* @typedef {"topLevel"} TopLevelInteropMessageIds
|
|
13
|
+
* @typedef {JSONRuleDefinition<{ MessageIds: TopLevelInteropMessageIds }>} TopLevelInteropRuleDefinition
|
|
14
|
+
*/
|
|
15
|
+
/** @type {TopLevelInteropRuleDefinition} */
|
|
16
|
+
declare const rule: TopLevelInteropRuleDefinition;
|
|
17
|
+
import type { JSONRuleDefinition } from "../types.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Rule to ensure top-level items are either an array or object.
|
|
3
|
+
* @author Joe Hildebrand
|
|
4
|
+
*/
|
|
5
|
+
//-----------------------------------------------------------------------------
|
|
6
|
+
// Type Definitions
|
|
7
|
+
//-----------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* @import { JSONRuleDefinition } from "../types.js";
|
|
10
|
+
* @typedef {"topLevel"} TopLevelInteropMessageIds
|
|
11
|
+
* @typedef {JSONRuleDefinition<{ MessageIds: TopLevelInteropMessageIds }>} TopLevelInteropRuleDefinition
|
|
12
|
+
*/
|
|
13
|
+
//-----------------------------------------------------------------------------
|
|
14
|
+
// Rule Definition
|
|
15
|
+
//-----------------------------------------------------------------------------
|
|
16
|
+
/** @type {TopLevelInteropRuleDefinition} */
|
|
17
|
+
const rule = {
|
|
18
|
+
meta: {
|
|
19
|
+
type: "problem",
|
|
20
|
+
docs: {
|
|
21
|
+
recommended: false,
|
|
22
|
+
description: "Require the JSON top-level value to be an array or object",
|
|
23
|
+
url: "https://github.com/eslint/json/tree/main/docs/rules/top-level-interop.md",
|
|
24
|
+
},
|
|
25
|
+
messages: {
|
|
26
|
+
topLevel: "Top level item should be array or object, got '{{type}}'.",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
create(context) {
|
|
30
|
+
return {
|
|
31
|
+
Document(node) {
|
|
32
|
+
const { type } = node.body;
|
|
33
|
+
if (type !== "Object" && type !== "Array") {
|
|
34
|
+
context.report({
|
|
35
|
+
loc: node.loc,
|
|
36
|
+
messageId: "topLevel",
|
|
37
|
+
data: { type },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export default rule;
|
package/dist/types.js
ADDED
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility Library
|
|
3
|
+
* @author 루밀LuMir(lumirlumir)
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @import { MemberNode } from "@humanwhocodes/momoa";
|
|
7
|
+
* @import { JSONSourceCode } from "./languages/json-source-code.js";
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Gets the `MemberNode`'s key value.
|
|
11
|
+
* @param {MemberNode} node The node to get the key from.
|
|
12
|
+
* @returns {string} The key value.
|
|
13
|
+
*/
|
|
14
|
+
export function getKey(node: MemberNode): string;
|
|
15
|
+
/**
|
|
16
|
+
* Gets the `MemberNode`'s raw key value.
|
|
17
|
+
* @param {MemberNode} node The node to get the raw key from.
|
|
18
|
+
* @param {JSONSourceCode} sourceCode The JSON source code object.
|
|
19
|
+
* @returns {string} The raw key value.
|
|
20
|
+
*/
|
|
21
|
+
export function getRawKey(node: MemberNode, sourceCode: JSONSourceCode): string;
|
|
22
|
+
import type { MemberNode } from "@humanwhocodes/momoa";
|
|
23
|
+
import type { JSONSourceCode } from "./languages/json-source-code.js";
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility Library
|
|
3
|
+
* @author 루밀LuMir(lumirlumir)
|
|
4
|
+
*/
|
|
5
|
+
//-----------------------------------------------------------------------------
|
|
6
|
+
// Type Definitions
|
|
7
|
+
//-----------------------------------------------------------------------------
|
|
8
|
+
/**
|
|
9
|
+
* @import { MemberNode } from "@humanwhocodes/momoa";
|
|
10
|
+
* @import { JSONSourceCode } from "./languages/json-source-code.js";
|
|
11
|
+
*/
|
|
12
|
+
//-----------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
//-----------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* Gets the `MemberNode`'s key value.
|
|
17
|
+
* @param {MemberNode} node The node to get the key from.
|
|
18
|
+
* @returns {string} The key value.
|
|
19
|
+
*/
|
|
20
|
+
export function getKey(node) {
|
|
21
|
+
return node.name.type === "String" ? node.name.value : node.name.name;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Gets the `MemberNode`'s raw key value.
|
|
25
|
+
* @param {MemberNode} node The node to get the raw key from.
|
|
26
|
+
* @param {JSONSourceCode} sourceCode The JSON source code object.
|
|
27
|
+
* @returns {string} The raw key value.
|
|
28
|
+
*/
|
|
29
|
+
export function getRawKey(node, sourceCode) {
|
|
30
|
+
return node.name.type === "String"
|
|
31
|
+
? sourceCode.getText(node.name, -1, -1)
|
|
32
|
+
: sourceCode.getText(node.name);
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint/json",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "JSON linting plugin for ESLint",
|
|
5
5
|
"author": "Nicholas C. Zakas",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"main": "dist/
|
|
8
|
-
"types": "dist/
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
"default": "./dist/cjs/index.cjs"
|
|
14
|
-
},
|
|
15
|
-
"import": {
|
|
16
|
-
"types": "./dist/esm/index.d.ts",
|
|
17
|
-
"default": "./dist/esm/index.js"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"./types": {
|
|
21
|
-
"require": {
|
|
22
|
-
"types": "./dist/cjs/types.cts"
|
|
23
|
-
},
|
|
24
|
-
"import": {
|
|
25
|
-
"types": "./dist/esm/types.d.ts"
|
|
26
|
-
}
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
27
13
|
}
|
|
28
14
|
},
|
|
29
15
|
"files": [
|
|
@@ -41,10 +27,7 @@
|
|
|
41
27
|
"prettier --write"
|
|
42
28
|
],
|
|
43
29
|
"!(*.js)": "prettier --write --ignore-unknown",
|
|
44
|
-
"README.md": [
|
|
45
|
-
"npm run build:update-rules-docs"
|
|
46
|
-
],
|
|
47
|
-
"{src/rules/*.js,tools/update-rules-docs.js}": [
|
|
30
|
+
"{src/rules/*.js,tools/update-rules-docs.js,README.md}": [
|
|
48
31
|
"npm run build:update-rules-docs",
|
|
49
32
|
"git add README.md"
|
|
50
33
|
]
|
|
@@ -58,20 +41,20 @@
|
|
|
58
41
|
},
|
|
59
42
|
"homepage": "https://github.com/eslint/json#readme",
|
|
60
43
|
"scripts": {
|
|
61
|
-
"build
|
|
62
|
-
"build:cts": "node tools/build-cts.js",
|
|
44
|
+
"build": "npm run build:rules && npm run build:types && npm run build:update-rules-docs",
|
|
63
45
|
"build:rules": "node tools/build-rules.js",
|
|
64
|
-
"build": "
|
|
46
|
+
"build:types": "tsc",
|
|
65
47
|
"build:update-rules-docs": "node tools/update-rules-docs.js",
|
|
66
48
|
"prepare": "npm run build",
|
|
67
49
|
"pretest": "npm run build",
|
|
68
50
|
"lint": "eslint",
|
|
69
51
|
"lint:fix": "eslint --fix",
|
|
52
|
+
"lint:types": "attw --pack --profile esm-only",
|
|
70
53
|
"fmt": "prettier --write .",
|
|
71
54
|
"fmt:check": "prettier --check .",
|
|
72
55
|
"test": "mocha \"tests/**/*.test.js\"",
|
|
73
56
|
"test:coverage": "c8 npm test",
|
|
74
|
-
"test:jsr": "npx jsr@latest publish --dry-run",
|
|
57
|
+
"test:jsr": "npx -y jsr@latest publish --dry-run",
|
|
75
58
|
"test:types": "tsc -p tests/types/tsconfig.json"
|
|
76
59
|
},
|
|
77
60
|
"keywords": [
|
|
@@ -83,28 +66,27 @@
|
|
|
83
66
|
],
|
|
84
67
|
"license": "Apache-2.0",
|
|
85
68
|
"dependencies": {
|
|
86
|
-
"@eslint/core": "^0.
|
|
87
|
-
"@eslint/plugin-kit": "^0.
|
|
69
|
+
"@eslint/core": "^1.0.1",
|
|
70
|
+
"@eslint/plugin-kit": "^0.5.1",
|
|
88
71
|
"@humanwhocodes/momoa": "^3.3.10",
|
|
89
72
|
"natural-compare": "^1.4.0"
|
|
90
73
|
},
|
|
91
74
|
"devDependencies": {
|
|
75
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
92
76
|
"c8": "^10.1.3",
|
|
93
77
|
"dedent": "^1.5.3",
|
|
94
|
-
"eslint": "^9.
|
|
78
|
+
"eslint": "^9.39.2",
|
|
95
79
|
"eslint-config-eslint": "^13.0.0",
|
|
96
80
|
"eslint-plugin-eslint-plugin": "^6.3.2",
|
|
97
|
-
"
|
|
81
|
+
"globals": "^17.0.0",
|
|
82
|
+
"lint-staged": "^16.0.0",
|
|
98
83
|
"mdast-util-from-markdown": "^2.0.2",
|
|
99
84
|
"mocha": "^11.3.0",
|
|
100
|
-
"prettier": "
|
|
101
|
-
"rollup": "^4.52.3",
|
|
102
|
-
"rollup-plugin-copy": "^3.5.0",
|
|
103
|
-
"rollup-plugin-delete": "^3.0.1",
|
|
85
|
+
"prettier": "3.8.1",
|
|
104
86
|
"typescript": "^5.9.2",
|
|
105
87
|
"yorkie": "^2.0.0"
|
|
106
88
|
},
|
|
107
89
|
"engines": {
|
|
108
|
-
"node": "^
|
|
90
|
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
|
109
91
|
}
|
|
110
92
|
}
|