@eslint/json 0.7.0 → 0.9.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 +16 -9
- package/dist/cjs/index.cjs +177 -16
- package/dist/cjs/index.d.cts +55 -4
- package/dist/esm/index.d.ts +55 -4
- package/dist/esm/index.js +177 -16
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -30,9 +30,9 @@ deno add @eslint/json
|
|
|
30
30
|
|
|
31
31
|
This package exports these languages:
|
|
32
32
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
33
|
+
- `"json/json"` is for regular JSON files
|
|
34
|
+
- `"json/jsonc"` is for JSON files that support comments ([JSONC](https://github.com/microsoft/node-jsonc-parser)) such as those used for Visual Studio Code configuration files
|
|
35
|
+
- `"json/json5"` is for [JSON5](https://json5.org) files
|
|
36
36
|
|
|
37
37
|
Depending on which types of JSON files you'd like to lint, you can set up your `eslint.config.js` file to include just the files you'd like. Here's an example that lints JSON, JSONC, and JSON5 files:
|
|
38
38
|
|
|
@@ -152,9 +152,16 @@ export default [
|
|
|
152
152
|
|
|
153
153
|
## Rules
|
|
154
154
|
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
155
|
+
- `no-duplicate-keys` - warns when there are two keys in an object with the same text.
|
|
156
|
+
- `no-empty-keys` - warns when there is a key in an object that is an empty string or contains only whitespace (note: `package-lock.json` uses empty keys intentionally)
|
|
157
|
+
- `no-unsafe-values` - warns on values that are unsafe for interchange, such
|
|
158
|
+
as strings with unmatched
|
|
159
|
+
[surrogates](https://en.wikipedia.org/wiki/UTF-16), numbers that evaluate to
|
|
160
|
+
Infinity, numbers that evaluate to zero unintentionally, numbers that look
|
|
161
|
+
like integers but are too large, and
|
|
162
|
+
[subnormal numbers](https://en.wikipedia.org/wiki/Subnormal_number).
|
|
163
|
+
- `no-unnormalized-keys` - warns on keys containing [unnormalized characters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize#description). You can optionally specify the normalization form via `{ form: "form_name" }`, where `form_name` can be any of `"NFC"`, `"NFD"`, `"NFKC"`, or `"NFKD"`.
|
|
164
|
+
- `top-level-interop` - warns when the top-level item in the document is neither an array nor an object. This can be enabled to ensure maximal interoperability with the oldest JSON parsers.
|
|
158
165
|
|
|
159
166
|
## Configuration Comments
|
|
160
167
|
|
|
@@ -221,8 +228,8 @@ export default [
|
|
|
221
228
|
|
|
222
229
|
This plugin implements JSON parsing for ESLint using the language plugins API, which is the official way of supporting non-JavaScript languages in ESLint. This differs from the other plugins:
|
|
223
230
|
|
|
224
|
-
-
|
|
225
|
-
-
|
|
231
|
+
- `eslint-plugin-json` uses a processor to parse the JSON, meaning it doesn't create an AST and you can't write custom rules for it.
|
|
232
|
+
- `eslint-plugin-jsonc` uses a parser that still goes through the JavaScript linting functionality and requires several rules to disallow valid JavaScript syntax that is invalid in JSON.
|
|
226
233
|
|
|
227
234
|
As such, this plugin is more robust and faster than the others. You can write your own custom rules when using the languages in this plugin, too.
|
|
228
235
|
|
|
@@ -250,7 +257,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
|
|
|
250
257
|
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
|
|
251
258
|
<p><a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a></p><h3>Silver Sponsors</h3>
|
|
252
259
|
<p><a href="https://www.serptriumph.com/"><img src="https://images.opencollective.com/serp-triumph5/fea3074/logo.png" alt="SERP Triumph" height="64"></a> <a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
|
|
253
|
-
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://www.wordhint.net/"><img src="https://images.opencollective.com/wordhint/be86813/avatar.png" alt="WordHint" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340?v=4" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a></p>
|
|
260
|
+
<p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://www.wordhint.net/"><img src="https://images.opencollective.com/wordhint/be86813/avatar.png" alt="WordHint" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340?v=4" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465?v=4" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a></p>
|
|
254
261
|
<h3>Technology Sponsors</h3>
|
|
255
262
|
Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
|
|
256
263
|
<p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -455,7 +455,7 @@ class JSONLanguage {
|
|
|
455
455
|
|
|
456
456
|
var noDuplicateKeys = {
|
|
457
457
|
meta: {
|
|
458
|
-
type: "problem",
|
|
458
|
+
type: /** @type {const} */ ("problem"),
|
|
459
459
|
|
|
460
460
|
docs: {
|
|
461
461
|
description: "Disallow duplicate keys in JSON objects",
|
|
@@ -508,7 +508,7 @@ var noDuplicateKeys = {
|
|
|
508
508
|
|
|
509
509
|
var noEmptyKeys = {
|
|
510
510
|
meta: {
|
|
511
|
-
type: "problem",
|
|
511
|
+
type: /** @type {const} */ ("problem"),
|
|
512
512
|
|
|
513
513
|
docs: {
|
|
514
514
|
description: "Disallow empty keys in JSON objects",
|
|
@@ -543,16 +543,27 @@ var noEmptyKeys = {
|
|
|
543
543
|
* @author Bradley Meck Farias
|
|
544
544
|
*/
|
|
545
545
|
|
|
546
|
+
// RFC 8259's `number` production, as a regex. Capture the integer part
|
|
547
|
+
// and the fractional part.
|
|
548
|
+
const NUMBER =
|
|
549
|
+
/^-?(?<int>0|([1-9][0-9]*))(?:\.(?<frac>[0-9]+))?(?:[eE][+-]?[0-9]+)?$/u;
|
|
550
|
+
const NON_ZERO = /[1-9]/u;
|
|
551
|
+
|
|
546
552
|
var noUnsafeValues = {
|
|
547
553
|
meta: {
|
|
548
|
-
type: "problem",
|
|
554
|
+
type: /** @type {const} */ ("problem"),
|
|
549
555
|
|
|
550
556
|
docs: {
|
|
551
557
|
description: "Disallow JSON values that are unsafe for interchange",
|
|
552
558
|
},
|
|
553
559
|
|
|
554
560
|
messages: {
|
|
555
|
-
unsafeNumber: "
|
|
561
|
+
unsafeNumber: "The number '{{ value }}' will evaluate to Infinity.",
|
|
562
|
+
unsafeInteger:
|
|
563
|
+
"The integer '{{ value }}' is outside the safe integer range.",
|
|
564
|
+
unsafeZero: "The number '{{ value }}' will evaluate to zero.",
|
|
565
|
+
subnormal:
|
|
566
|
+
"Unexpected subnormal number '{{ value }}' found, which may cause interoperability issues.",
|
|
556
567
|
loneSurrogate: "Lone surrogate '{{ surrogate }}' found.",
|
|
557
568
|
},
|
|
558
569
|
},
|
|
@@ -560,11 +571,64 @@ var noUnsafeValues = {
|
|
|
560
571
|
create(context) {
|
|
561
572
|
return {
|
|
562
573
|
Number(node) {
|
|
574
|
+
const value = context.sourceCode.getText(node);
|
|
575
|
+
|
|
563
576
|
if (Number.isFinite(node.value) !== true) {
|
|
564
577
|
context.report({
|
|
565
578
|
loc: node.loc,
|
|
566
579
|
messageId: "unsafeNumber",
|
|
580
|
+
data: { value },
|
|
567
581
|
});
|
|
582
|
+
} else {
|
|
583
|
+
// Also matches -0, intentionally
|
|
584
|
+
if (node.value === 0) {
|
|
585
|
+
// If the value has been rounded down to 0, but there was some
|
|
586
|
+
// fraction or non-zero part before the e-, this is a very small
|
|
587
|
+
// number that doesn't fit inside an f64.
|
|
588
|
+
const match = value.match(NUMBER);
|
|
589
|
+
// assert(match, "If the regex is right, match is always truthy")
|
|
590
|
+
|
|
591
|
+
// If any part of the number other than the exponent has a
|
|
592
|
+
// non-zero digit in it, this number was not intended to be
|
|
593
|
+
// evaluated down to a zero.
|
|
594
|
+
if (
|
|
595
|
+
NON_ZERO.test(match.groups.int) ||
|
|
596
|
+
NON_ZERO.test(match.groups.frac)
|
|
597
|
+
) {
|
|
598
|
+
context.report({
|
|
599
|
+
loc: node.loc,
|
|
600
|
+
messageId: "unsafeZero",
|
|
601
|
+
data: { value },
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
} else if (!/[.e]/iu.test(value)) {
|
|
605
|
+
// Intended to be an integer
|
|
606
|
+
if (
|
|
607
|
+
node.value > Number.MAX_SAFE_INTEGER ||
|
|
608
|
+
node.value < Number.MIN_SAFE_INTEGER
|
|
609
|
+
) {
|
|
610
|
+
context.report({
|
|
611
|
+
loc: node.loc,
|
|
612
|
+
messageId: "unsafeInteger",
|
|
613
|
+
data: { value },
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
// Floating point. Check for subnormal.
|
|
618
|
+
const buffer = new ArrayBuffer(8);
|
|
619
|
+
const view = new DataView(buffer);
|
|
620
|
+
view.setFloat64(0, node.value, false);
|
|
621
|
+
const asBigInt = view.getBigUint64(0, false);
|
|
622
|
+
// Subnormals have an 11-bit exponent of 0 and a non-zero mantissa.
|
|
623
|
+
if ((asBigInt & 0x7ff0000000000000n) === 0n) {
|
|
624
|
+
context.report({
|
|
625
|
+
loc: node.loc,
|
|
626
|
+
messageId: "subnormal",
|
|
627
|
+
// Value included so that it's seen in scientific notation
|
|
628
|
+
data: node,
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
568
632
|
}
|
|
569
633
|
},
|
|
570
634
|
String(node) {
|
|
@@ -599,6 +663,97 @@ var noUnsafeValues = {
|
|
|
599
663
|
},
|
|
600
664
|
};
|
|
601
665
|
|
|
666
|
+
/**
|
|
667
|
+
* @fileoverview Rule to detect unnormalized keys in JSON.
|
|
668
|
+
* @author Bradley Meck Farias
|
|
669
|
+
*/
|
|
670
|
+
|
|
671
|
+
var noUnnormalizedKeys = {
|
|
672
|
+
meta: {
|
|
673
|
+
type: /** @type {const} */ ("problem"),
|
|
674
|
+
|
|
675
|
+
docs: {
|
|
676
|
+
description: "Disallow JSON keys that are not normalized",
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
messages: {
|
|
680
|
+
unnormalizedKey: "Unnormalized key '{{key}}' found.",
|
|
681
|
+
},
|
|
682
|
+
|
|
683
|
+
schema: [
|
|
684
|
+
{
|
|
685
|
+
type: "object",
|
|
686
|
+
properties: {
|
|
687
|
+
form: {
|
|
688
|
+
enum: ["NFC", "NFD", "NFKC", "NFKD"],
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
additionalProperties: false,
|
|
692
|
+
},
|
|
693
|
+
],
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
create(context) {
|
|
697
|
+
const normalization = context.options.length
|
|
698
|
+
? text => text.normalize(context.options[0].form)
|
|
699
|
+
: text => text.normalize();
|
|
700
|
+
return {
|
|
701
|
+
Member(node) {
|
|
702
|
+
const key =
|
|
703
|
+
node.name.type === "String"
|
|
704
|
+
? node.name.value
|
|
705
|
+
: node.name.name;
|
|
706
|
+
|
|
707
|
+
if (normalization(key) !== key) {
|
|
708
|
+
context.report({
|
|
709
|
+
loc: node.name.loc,
|
|
710
|
+
messageId: "unnormalizedKey",
|
|
711
|
+
data: {
|
|
712
|
+
key,
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
};
|
|
718
|
+
},
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* @fileoverview Rule to ensure top-level items are either an array or ojbect.
|
|
723
|
+
* @author Joe Hildebrand
|
|
724
|
+
*/
|
|
725
|
+
|
|
726
|
+
var topLevelInterop = {
|
|
727
|
+
meta: {
|
|
728
|
+
type: /** @type {const} */ ("problem"),
|
|
729
|
+
|
|
730
|
+
docs: {
|
|
731
|
+
description:
|
|
732
|
+
"Require the JSON top-level value to be an array or object",
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
messages: {
|
|
736
|
+
topLevel:
|
|
737
|
+
"Top level item should be array or object, got '{{type}}'.",
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
create(context) {
|
|
742
|
+
return {
|
|
743
|
+
Document(node) {
|
|
744
|
+
const { type } = node.body;
|
|
745
|
+
if (type !== "Object" && type !== "Array") {
|
|
746
|
+
context.report({
|
|
747
|
+
loc: node.loc,
|
|
748
|
+
messageId: "topLevel",
|
|
749
|
+
data: { type },
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
};
|
|
754
|
+
},
|
|
755
|
+
};
|
|
756
|
+
|
|
602
757
|
/**
|
|
603
758
|
* @fileoverview JSON plugin.
|
|
604
759
|
* @author Nicholas C. Zakas
|
|
@@ -612,7 +767,7 @@ var noUnsafeValues = {
|
|
|
612
767
|
const plugin = {
|
|
613
768
|
meta: {
|
|
614
769
|
name: "@eslint/json",
|
|
615
|
-
version: "0.
|
|
770
|
+
version: "0.9.0", // x-release-please-version
|
|
616
771
|
},
|
|
617
772
|
languages: {
|
|
618
773
|
json: new JSONLanguage({ mode: "json" }),
|
|
@@ -623,20 +778,26 @@ const plugin = {
|
|
|
623
778
|
"no-duplicate-keys": noDuplicateKeys,
|
|
624
779
|
"no-empty-keys": noEmptyKeys,
|
|
625
780
|
"no-unsafe-values": noUnsafeValues,
|
|
781
|
+
"no-unnormalized-keys": noUnnormalizedKeys,
|
|
782
|
+
"top-level-interop": topLevelInterop,
|
|
626
783
|
},
|
|
627
|
-
configs: {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
"json/no-unsafe-values": "error",
|
|
784
|
+
configs: {
|
|
785
|
+
recommended: {
|
|
786
|
+
plugins: {},
|
|
787
|
+
rules: /** @type {const} */ ({
|
|
788
|
+
"json/no-duplicate-keys": "error",
|
|
789
|
+
"json/no-empty-keys": "error",
|
|
790
|
+
"json/no-unsafe-values": "error",
|
|
791
|
+
"json/no-unnormalized-keys": "error",
|
|
792
|
+
}),
|
|
637
793
|
},
|
|
638
794
|
},
|
|
639
|
-
}
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// eslint-disable-next-line no-lone-blocks -- The block syntax { ... } ensures that TypeScript does not get confused about the type of `plugin`.
|
|
798
|
+
{
|
|
799
|
+
plugin.configs.recommended.plugins.json = plugin;
|
|
800
|
+
}
|
|
640
801
|
|
|
641
802
|
exports.JSONLanguage = JSONLanguage;
|
|
642
803
|
exports.JSONSourceCode = JSONSourceCode;
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -174,7 +174,7 @@ declare namespace plugin {
|
|
|
174
174
|
let rules: {
|
|
175
175
|
"no-duplicate-keys": {
|
|
176
176
|
meta: {
|
|
177
|
-
type:
|
|
177
|
+
type: "problem";
|
|
178
178
|
docs: {
|
|
179
179
|
description: string;
|
|
180
180
|
};
|
|
@@ -190,7 +190,7 @@ declare namespace plugin {
|
|
|
190
190
|
};
|
|
191
191
|
"no-empty-keys": {
|
|
192
192
|
meta: {
|
|
193
|
-
type:
|
|
193
|
+
type: "problem";
|
|
194
194
|
docs: {
|
|
195
195
|
description: string;
|
|
196
196
|
};
|
|
@@ -204,12 +204,15 @@ declare namespace plugin {
|
|
|
204
204
|
};
|
|
205
205
|
"no-unsafe-values": {
|
|
206
206
|
meta: {
|
|
207
|
-
type:
|
|
207
|
+
type: "problem";
|
|
208
208
|
docs: {
|
|
209
209
|
description: string;
|
|
210
210
|
};
|
|
211
211
|
messages: {
|
|
212
212
|
unsafeNumber: string;
|
|
213
|
+
unsafeInteger: string;
|
|
214
|
+
unsafeZero: string;
|
|
215
|
+
subnormal: string;
|
|
213
216
|
loneSurrogate: string;
|
|
214
217
|
};
|
|
215
218
|
};
|
|
@@ -218,8 +221,56 @@ declare namespace plugin {
|
|
|
218
221
|
String(node: any): void;
|
|
219
222
|
};
|
|
220
223
|
};
|
|
224
|
+
"no-unnormalized-keys": {
|
|
225
|
+
meta: {
|
|
226
|
+
type: "problem";
|
|
227
|
+
docs: {
|
|
228
|
+
description: string;
|
|
229
|
+
};
|
|
230
|
+
messages: {
|
|
231
|
+
unnormalizedKey: string;
|
|
232
|
+
};
|
|
233
|
+
schema: {
|
|
234
|
+
type: string;
|
|
235
|
+
properties: {
|
|
236
|
+
form: {
|
|
237
|
+
enum: string[];
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
additionalProperties: boolean;
|
|
241
|
+
}[];
|
|
242
|
+
};
|
|
243
|
+
create(context: any): {
|
|
244
|
+
Member(node: any): void;
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
"top-level-interop": {
|
|
248
|
+
meta: {
|
|
249
|
+
type: "problem";
|
|
250
|
+
docs: {
|
|
251
|
+
description: string;
|
|
252
|
+
};
|
|
253
|
+
messages: {
|
|
254
|
+
topLevel: string;
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
create(context: any): {
|
|
258
|
+
Document(node: any): void;
|
|
259
|
+
};
|
|
260
|
+
};
|
|
221
261
|
};
|
|
222
|
-
|
|
262
|
+
namespace configs {
|
|
263
|
+
namespace recommended {
|
|
264
|
+
export let plugins: {};
|
|
265
|
+
let rules_1: {
|
|
266
|
+
readonly "json/no-duplicate-keys": "error";
|
|
267
|
+
readonly "json/no-empty-keys": "error";
|
|
268
|
+
readonly "json/no-unsafe-values": "error";
|
|
269
|
+
readonly "json/no-unnormalized-keys": "error";
|
|
270
|
+
};
|
|
271
|
+
export { rules_1 as rules };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
223
274
|
}
|
|
224
275
|
import { TextSourceCodeBase } from '@eslint/plugin-kit';
|
|
225
276
|
import { Directive } from '@eslint/plugin-kit';
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -174,7 +174,7 @@ declare namespace plugin {
|
|
|
174
174
|
let rules: {
|
|
175
175
|
"no-duplicate-keys": {
|
|
176
176
|
meta: {
|
|
177
|
-
type:
|
|
177
|
+
type: "problem";
|
|
178
178
|
docs: {
|
|
179
179
|
description: string;
|
|
180
180
|
};
|
|
@@ -190,7 +190,7 @@ declare namespace plugin {
|
|
|
190
190
|
};
|
|
191
191
|
"no-empty-keys": {
|
|
192
192
|
meta: {
|
|
193
|
-
type:
|
|
193
|
+
type: "problem";
|
|
194
194
|
docs: {
|
|
195
195
|
description: string;
|
|
196
196
|
};
|
|
@@ -204,12 +204,15 @@ declare namespace plugin {
|
|
|
204
204
|
};
|
|
205
205
|
"no-unsafe-values": {
|
|
206
206
|
meta: {
|
|
207
|
-
type:
|
|
207
|
+
type: "problem";
|
|
208
208
|
docs: {
|
|
209
209
|
description: string;
|
|
210
210
|
};
|
|
211
211
|
messages: {
|
|
212
212
|
unsafeNumber: string;
|
|
213
|
+
unsafeInteger: string;
|
|
214
|
+
unsafeZero: string;
|
|
215
|
+
subnormal: string;
|
|
213
216
|
loneSurrogate: string;
|
|
214
217
|
};
|
|
215
218
|
};
|
|
@@ -218,8 +221,56 @@ declare namespace plugin {
|
|
|
218
221
|
String(node: any): void;
|
|
219
222
|
};
|
|
220
223
|
};
|
|
224
|
+
"no-unnormalized-keys": {
|
|
225
|
+
meta: {
|
|
226
|
+
type: "problem";
|
|
227
|
+
docs: {
|
|
228
|
+
description: string;
|
|
229
|
+
};
|
|
230
|
+
messages: {
|
|
231
|
+
unnormalizedKey: string;
|
|
232
|
+
};
|
|
233
|
+
schema: {
|
|
234
|
+
type: string;
|
|
235
|
+
properties: {
|
|
236
|
+
form: {
|
|
237
|
+
enum: string[];
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
additionalProperties: boolean;
|
|
241
|
+
}[];
|
|
242
|
+
};
|
|
243
|
+
create(context: any): {
|
|
244
|
+
Member(node: any): void;
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
"top-level-interop": {
|
|
248
|
+
meta: {
|
|
249
|
+
type: "problem";
|
|
250
|
+
docs: {
|
|
251
|
+
description: string;
|
|
252
|
+
};
|
|
253
|
+
messages: {
|
|
254
|
+
topLevel: string;
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
create(context: any): {
|
|
258
|
+
Document(node: any): void;
|
|
259
|
+
};
|
|
260
|
+
};
|
|
221
261
|
};
|
|
222
|
-
|
|
262
|
+
namespace configs {
|
|
263
|
+
namespace recommended {
|
|
264
|
+
export let plugins: {};
|
|
265
|
+
let rules_1: {
|
|
266
|
+
readonly "json/no-duplicate-keys": "error";
|
|
267
|
+
readonly "json/no-empty-keys": "error";
|
|
268
|
+
readonly "json/no-unsafe-values": "error";
|
|
269
|
+
readonly "json/no-unnormalized-keys": "error";
|
|
270
|
+
};
|
|
271
|
+
export { rules_1 as rules };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
223
274
|
}
|
|
224
275
|
import { TextSourceCodeBase } from '@eslint/plugin-kit';
|
|
225
276
|
import { Directive } from '@eslint/plugin-kit';
|
package/dist/esm/index.js
CHANGED
|
@@ -452,7 +452,7 @@ class JSONLanguage {
|
|
|
452
452
|
|
|
453
453
|
var noDuplicateKeys = {
|
|
454
454
|
meta: {
|
|
455
|
-
type: "problem",
|
|
455
|
+
type: /** @type {const} */ ("problem"),
|
|
456
456
|
|
|
457
457
|
docs: {
|
|
458
458
|
description: "Disallow duplicate keys in JSON objects",
|
|
@@ -505,7 +505,7 @@ var noDuplicateKeys = {
|
|
|
505
505
|
|
|
506
506
|
var noEmptyKeys = {
|
|
507
507
|
meta: {
|
|
508
|
-
type: "problem",
|
|
508
|
+
type: /** @type {const} */ ("problem"),
|
|
509
509
|
|
|
510
510
|
docs: {
|
|
511
511
|
description: "Disallow empty keys in JSON objects",
|
|
@@ -540,16 +540,27 @@ var noEmptyKeys = {
|
|
|
540
540
|
* @author Bradley Meck Farias
|
|
541
541
|
*/
|
|
542
542
|
|
|
543
|
+
// RFC 8259's `number` production, as a regex. Capture the integer part
|
|
544
|
+
// and the fractional part.
|
|
545
|
+
const NUMBER =
|
|
546
|
+
/^-?(?<int>0|([1-9][0-9]*))(?:\.(?<frac>[0-9]+))?(?:[eE][+-]?[0-9]+)?$/u;
|
|
547
|
+
const NON_ZERO = /[1-9]/u;
|
|
548
|
+
|
|
543
549
|
var noUnsafeValues = {
|
|
544
550
|
meta: {
|
|
545
|
-
type: "problem",
|
|
551
|
+
type: /** @type {const} */ ("problem"),
|
|
546
552
|
|
|
547
553
|
docs: {
|
|
548
554
|
description: "Disallow JSON values that are unsafe for interchange",
|
|
549
555
|
},
|
|
550
556
|
|
|
551
557
|
messages: {
|
|
552
|
-
unsafeNumber: "
|
|
558
|
+
unsafeNumber: "The number '{{ value }}' will evaluate to Infinity.",
|
|
559
|
+
unsafeInteger:
|
|
560
|
+
"The integer '{{ value }}' is outside the safe integer range.",
|
|
561
|
+
unsafeZero: "The number '{{ value }}' will evaluate to zero.",
|
|
562
|
+
subnormal:
|
|
563
|
+
"Unexpected subnormal number '{{ value }}' found, which may cause interoperability issues.",
|
|
553
564
|
loneSurrogate: "Lone surrogate '{{ surrogate }}' found.",
|
|
554
565
|
},
|
|
555
566
|
},
|
|
@@ -557,11 +568,64 @@ var noUnsafeValues = {
|
|
|
557
568
|
create(context) {
|
|
558
569
|
return {
|
|
559
570
|
Number(node) {
|
|
571
|
+
const value = context.sourceCode.getText(node);
|
|
572
|
+
|
|
560
573
|
if (Number.isFinite(node.value) !== true) {
|
|
561
574
|
context.report({
|
|
562
575
|
loc: node.loc,
|
|
563
576
|
messageId: "unsafeNumber",
|
|
577
|
+
data: { value },
|
|
564
578
|
});
|
|
579
|
+
} else {
|
|
580
|
+
// Also matches -0, intentionally
|
|
581
|
+
if (node.value === 0) {
|
|
582
|
+
// If the value has been rounded down to 0, but there was some
|
|
583
|
+
// fraction or non-zero part before the e-, this is a very small
|
|
584
|
+
// number that doesn't fit inside an f64.
|
|
585
|
+
const match = value.match(NUMBER);
|
|
586
|
+
// assert(match, "If the regex is right, match is always truthy")
|
|
587
|
+
|
|
588
|
+
// If any part of the number other than the exponent has a
|
|
589
|
+
// non-zero digit in it, this number was not intended to be
|
|
590
|
+
// evaluated down to a zero.
|
|
591
|
+
if (
|
|
592
|
+
NON_ZERO.test(match.groups.int) ||
|
|
593
|
+
NON_ZERO.test(match.groups.frac)
|
|
594
|
+
) {
|
|
595
|
+
context.report({
|
|
596
|
+
loc: node.loc,
|
|
597
|
+
messageId: "unsafeZero",
|
|
598
|
+
data: { value },
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
} else if (!/[.e]/iu.test(value)) {
|
|
602
|
+
// Intended to be an integer
|
|
603
|
+
if (
|
|
604
|
+
node.value > Number.MAX_SAFE_INTEGER ||
|
|
605
|
+
node.value < Number.MIN_SAFE_INTEGER
|
|
606
|
+
) {
|
|
607
|
+
context.report({
|
|
608
|
+
loc: node.loc,
|
|
609
|
+
messageId: "unsafeInteger",
|
|
610
|
+
data: { value },
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
// Floating point. Check for subnormal.
|
|
615
|
+
const buffer = new ArrayBuffer(8);
|
|
616
|
+
const view = new DataView(buffer);
|
|
617
|
+
view.setFloat64(0, node.value, false);
|
|
618
|
+
const asBigInt = view.getBigUint64(0, false);
|
|
619
|
+
// Subnormals have an 11-bit exponent of 0 and a non-zero mantissa.
|
|
620
|
+
if ((asBigInt & 0x7ff0000000000000n) === 0n) {
|
|
621
|
+
context.report({
|
|
622
|
+
loc: node.loc,
|
|
623
|
+
messageId: "subnormal",
|
|
624
|
+
// Value included so that it's seen in scientific notation
|
|
625
|
+
data: node,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
}
|
|
565
629
|
}
|
|
566
630
|
},
|
|
567
631
|
String(node) {
|
|
@@ -596,6 +660,97 @@ var noUnsafeValues = {
|
|
|
596
660
|
},
|
|
597
661
|
};
|
|
598
662
|
|
|
663
|
+
/**
|
|
664
|
+
* @fileoverview Rule to detect unnormalized keys in JSON.
|
|
665
|
+
* @author Bradley Meck Farias
|
|
666
|
+
*/
|
|
667
|
+
|
|
668
|
+
var noUnnormalizedKeys = {
|
|
669
|
+
meta: {
|
|
670
|
+
type: /** @type {const} */ ("problem"),
|
|
671
|
+
|
|
672
|
+
docs: {
|
|
673
|
+
description: "Disallow JSON keys that are not normalized",
|
|
674
|
+
},
|
|
675
|
+
|
|
676
|
+
messages: {
|
|
677
|
+
unnormalizedKey: "Unnormalized key '{{key}}' found.",
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
schema: [
|
|
681
|
+
{
|
|
682
|
+
type: "object",
|
|
683
|
+
properties: {
|
|
684
|
+
form: {
|
|
685
|
+
enum: ["NFC", "NFD", "NFKC", "NFKD"],
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
additionalProperties: false,
|
|
689
|
+
},
|
|
690
|
+
],
|
|
691
|
+
},
|
|
692
|
+
|
|
693
|
+
create(context) {
|
|
694
|
+
const normalization = context.options.length
|
|
695
|
+
? text => text.normalize(context.options[0].form)
|
|
696
|
+
: text => text.normalize();
|
|
697
|
+
return {
|
|
698
|
+
Member(node) {
|
|
699
|
+
const key =
|
|
700
|
+
node.name.type === "String"
|
|
701
|
+
? node.name.value
|
|
702
|
+
: node.name.name;
|
|
703
|
+
|
|
704
|
+
if (normalization(key) !== key) {
|
|
705
|
+
context.report({
|
|
706
|
+
loc: node.name.loc,
|
|
707
|
+
messageId: "unnormalizedKey",
|
|
708
|
+
data: {
|
|
709
|
+
key,
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* @fileoverview Rule to ensure top-level items are either an array or ojbect.
|
|
720
|
+
* @author Joe Hildebrand
|
|
721
|
+
*/
|
|
722
|
+
|
|
723
|
+
var topLevelInterop = {
|
|
724
|
+
meta: {
|
|
725
|
+
type: /** @type {const} */ ("problem"),
|
|
726
|
+
|
|
727
|
+
docs: {
|
|
728
|
+
description:
|
|
729
|
+
"Require the JSON top-level value to be an array or object",
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
messages: {
|
|
733
|
+
topLevel:
|
|
734
|
+
"Top level item should be array or object, got '{{type}}'.",
|
|
735
|
+
},
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
create(context) {
|
|
739
|
+
return {
|
|
740
|
+
Document(node) {
|
|
741
|
+
const { type } = node.body;
|
|
742
|
+
if (type !== "Object" && type !== "Array") {
|
|
743
|
+
context.report({
|
|
744
|
+
loc: node.loc,
|
|
745
|
+
messageId: "topLevel",
|
|
746
|
+
data: { type },
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
|
|
599
754
|
/**
|
|
600
755
|
* @fileoverview JSON plugin.
|
|
601
756
|
* @author Nicholas C. Zakas
|
|
@@ -609,7 +764,7 @@ var noUnsafeValues = {
|
|
|
609
764
|
const plugin = {
|
|
610
765
|
meta: {
|
|
611
766
|
name: "@eslint/json",
|
|
612
|
-
version: "0.
|
|
767
|
+
version: "0.9.0", // x-release-please-version
|
|
613
768
|
},
|
|
614
769
|
languages: {
|
|
615
770
|
json: new JSONLanguage({ mode: "json" }),
|
|
@@ -620,19 +775,25 @@ const plugin = {
|
|
|
620
775
|
"no-duplicate-keys": noDuplicateKeys,
|
|
621
776
|
"no-empty-keys": noEmptyKeys,
|
|
622
777
|
"no-unsafe-values": noUnsafeValues,
|
|
778
|
+
"no-unnormalized-keys": noUnnormalizedKeys,
|
|
779
|
+
"top-level-interop": topLevelInterop,
|
|
623
780
|
},
|
|
624
|
-
configs: {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
"json/no-unsafe-values": "error",
|
|
781
|
+
configs: {
|
|
782
|
+
recommended: {
|
|
783
|
+
plugins: {},
|
|
784
|
+
rules: /** @type {const} */ ({
|
|
785
|
+
"json/no-duplicate-keys": "error",
|
|
786
|
+
"json/no-empty-keys": "error",
|
|
787
|
+
"json/no-unsafe-values": "error",
|
|
788
|
+
"json/no-unnormalized-keys": "error",
|
|
789
|
+
}),
|
|
634
790
|
},
|
|
635
791
|
},
|
|
636
|
-
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// eslint-disable-next-line no-lone-blocks -- The block syntax { ... } ensures that TypeScript does not get confused about the type of `plugin`.
|
|
795
|
+
{
|
|
796
|
+
plugin.configs.recommended.plugins.json = plugin;
|
|
797
|
+
}
|
|
637
798
|
|
|
638
799
|
export { JSONLanguage, JSONSourceCode, plugin as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint/json",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "JSON linting plugin for ESLint",
|
|
5
5
|
"author": "Nicholas C. Zakas",
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"fmt": "prettier --write .",
|
|
52
52
|
"fmt:check": "prettier --check .",
|
|
53
53
|
"test": "mocha tests/**/*.js",
|
|
54
|
-
"test:coverage": "c8 npm test"
|
|
54
|
+
"test:coverage": "c8 npm test",
|
|
55
|
+
"test:types": "tsc -p tests/types/tsconfig.json"
|
|
55
56
|
},
|
|
56
57
|
"keywords": [
|
|
57
58
|
"eslint",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"license": "Apache-2.0",
|
|
64
65
|
"dependencies": {
|
|
65
66
|
"@eslint/plugin-kit": "^0.2.3",
|
|
66
|
-
"@humanwhocodes/momoa": "^3.3.
|
|
67
|
+
"@humanwhocodes/momoa": "^3.3.4"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
69
70
|
"@eslint/core": "^0.6.0",
|
|
@@ -72,10 +73,11 @@
|
|
|
72
73
|
"dedent": "^1.5.3",
|
|
73
74
|
"eslint": "^9.11.1",
|
|
74
75
|
"eslint-config-eslint": "^11.0.0",
|
|
76
|
+
"eslint-plugin-eslint-plugin": "^6.3.2",
|
|
75
77
|
"got": "^14.4.2",
|
|
76
78
|
"lint-staged": "^15.2.7",
|
|
77
79
|
"mocha": "^10.4.0",
|
|
78
|
-
"prettier": "^3.
|
|
80
|
+
"prettier": "^3.4.1",
|
|
79
81
|
"rollup": "^4.16.2",
|
|
80
82
|
"rollup-plugin-copy": "^3.5.0",
|
|
81
83
|
"typescript": "^5.4.5",
|