@eslint/json 0.6.0 → 0.8.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 CHANGED
@@ -154,6 +154,8 @@ export default [
154
154
 
155
155
  - `no-duplicate-keys` - warns when there are two keys in an object with the same text.
156
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 as numbers outside safe range or lone surrogates.
158
+ - `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"`.
157
159
 
158
160
  ## Configuration Comments
159
161
 
@@ -249,7 +251,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
249
251
  <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>
250
252
  <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>
251
253
  <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>
252
- <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://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>
254
+ <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>
253
255
  <h3>Technology Sponsors</h3>
254
256
  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.
255
257
  <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>
@@ -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",
@@ -538,6 +538,122 @@ var noEmptyKeys = {
538
538
  },
539
539
  };
540
540
 
541
+ /**
542
+ * @fileoverview Rule to detect unsafe values in JSON.
543
+ * @author Bradley Meck Farias
544
+ */
545
+
546
+ var noUnsafeValues = {
547
+ meta: {
548
+ type: /** @type {const} */ ("problem"),
549
+
550
+ docs: {
551
+ description: "Disallow JSON values that are unsafe for interchange",
552
+ },
553
+
554
+ messages: {
555
+ unsafeNumber: "Number outside safe range found.",
556
+ loneSurrogate: "Lone surrogate '{{ surrogate }}' found.",
557
+ },
558
+ },
559
+
560
+ create(context) {
561
+ return {
562
+ Number(node) {
563
+ if (Number.isFinite(node.value) !== true) {
564
+ context.report({
565
+ loc: node.loc,
566
+ messageId: "unsafeNumber",
567
+ });
568
+ }
569
+ },
570
+ String(node) {
571
+ if (node.value.isWellFormed) {
572
+ if (node.value.isWellFormed()) {
573
+ return;
574
+ }
575
+ }
576
+ // match any high surrogate and, if it exists, a paired low surrogate
577
+ // match any low surrogate not already matched
578
+ const surrogatePattern =
579
+ /[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/gu;
580
+ let match = surrogatePattern.exec(node.value);
581
+ while (match) {
582
+ // only need to report non-paired surrogates
583
+ if (match[0].length < 2) {
584
+ context.report({
585
+ loc: node.loc,
586
+ messageId: "loneSurrogate",
587
+ data: {
588
+ surrogate: JSON.stringify(match[0]).slice(
589
+ 1,
590
+ -1,
591
+ ),
592
+ },
593
+ });
594
+ }
595
+ match = surrogatePattern.exec(node.value);
596
+ }
597
+ },
598
+ };
599
+ },
600
+ };
601
+
602
+ /**
603
+ * @fileoverview Rule to detect unnormalized keys in JSON.
604
+ * @author Bradley Meck Farias
605
+ */
606
+
607
+ var noUnnormalizedKeys = {
608
+ meta: {
609
+ type: /** @type {const} */ ("problem"),
610
+
611
+ docs: {
612
+ description: "Disallow JSON keys that are not normalized",
613
+ },
614
+
615
+ messages: {
616
+ unnormalizedKey: "Unnormalized key '{{key}}' found.",
617
+ },
618
+
619
+ schema: [
620
+ {
621
+ type: "object",
622
+ properties: {
623
+ form: {
624
+ enum: ["NFC", "NFD", "NFKC", "NFKD"],
625
+ },
626
+ },
627
+ additionalProperties: false,
628
+ },
629
+ ],
630
+ },
631
+
632
+ create(context) {
633
+ const normalization = context.options.length
634
+ ? text => text.normalize(context.options[0].form)
635
+ : text => text.normalize();
636
+ return {
637
+ Member(node) {
638
+ const key =
639
+ node.name.type === "String"
640
+ ? node.name.value
641
+ : node.name.name;
642
+
643
+ if (normalization(key) !== key) {
644
+ context.report({
645
+ loc: node.name.loc,
646
+ messageId: "unnormalizedKey",
647
+ data: {
648
+ key,
649
+ },
650
+ });
651
+ }
652
+ },
653
+ };
654
+ },
655
+ };
656
+
541
657
  /**
542
658
  * @fileoverview JSON plugin.
543
659
  * @author Nicholas C. Zakas
@@ -551,7 +667,7 @@ var noEmptyKeys = {
551
667
  const plugin = {
552
668
  meta: {
553
669
  name: "@eslint/json",
554
- version: "0.6.0", // x-release-please-version
670
+ version: "0.8.0", // x-release-please-version
555
671
  },
556
672
  languages: {
557
673
  json: new JSONLanguage({ mode: "json" }),
@@ -561,19 +677,26 @@ const plugin = {
561
677
  rules: {
562
678
  "no-duplicate-keys": noDuplicateKeys,
563
679
  "no-empty-keys": noEmptyKeys,
680
+ "no-unsafe-values": noUnsafeValues,
681
+ "no-unnormalized-keys": noUnnormalizedKeys,
564
682
  },
565
- configs: {},
566
- };
567
-
568
- Object.assign(plugin.configs, {
569
- recommended: {
570
- plugins: { json: plugin },
571
- rules: {
572
- "json/no-duplicate-keys": "error",
573
- "json/no-empty-keys": "error",
683
+ configs: {
684
+ recommended: {
685
+ plugins: {},
686
+ rules: /** @type {const} */ ({
687
+ "json/no-duplicate-keys": "error",
688
+ "json/no-empty-keys": "error",
689
+ "json/no-unsafe-values": "error",
690
+ "json/no-unnormalized-keys": "error",
691
+ }),
574
692
  },
575
693
  },
576
- });
694
+ };
695
+
696
+ // eslint-disable-next-line no-lone-blocks -- The block syntax { ... } ensures that TypeScript does not get confused about the type of `plugin`.
697
+ {
698
+ plugin.configs.recommended.plugins.json = plugin;
699
+ }
577
700
 
578
701
  exports.JSONLanguage = JSONLanguage;
579
702
  exports.JSONSourceCode = JSONSourceCode;
@@ -174,7 +174,7 @@ declare namespace plugin {
174
174
  let rules: {
175
175
  "no-duplicate-keys": {
176
176
  meta: {
177
- type: string;
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: string;
193
+ type: "problem";
194
194
  docs: {
195
195
  description: string;
196
196
  };
@@ -202,8 +202,58 @@ declare namespace plugin {
202
202
  Member(node: any): void;
203
203
  };
204
204
  };
205
+ "no-unsafe-values": {
206
+ meta: {
207
+ type: "problem";
208
+ docs: {
209
+ description: string;
210
+ };
211
+ messages: {
212
+ unsafeNumber: string;
213
+ loneSurrogate: string;
214
+ };
215
+ };
216
+ create(context: any): {
217
+ Number(node: any): void;
218
+ String(node: any): void;
219
+ };
220
+ };
221
+ "no-unnormalized-keys": {
222
+ meta: {
223
+ type: "problem";
224
+ docs: {
225
+ description: string;
226
+ };
227
+ messages: {
228
+ unnormalizedKey: string;
229
+ };
230
+ schema: {
231
+ type: string;
232
+ properties: {
233
+ form: {
234
+ enum: string[];
235
+ };
236
+ };
237
+ additionalProperties: boolean;
238
+ }[];
239
+ };
240
+ create(context: any): {
241
+ Member(node: any): void;
242
+ };
243
+ };
205
244
  };
206
- let configs: {};
245
+ namespace configs {
246
+ namespace recommended {
247
+ export let plugins: {};
248
+ let rules_1: {
249
+ readonly "json/no-duplicate-keys": "error";
250
+ readonly "json/no-empty-keys": "error";
251
+ readonly "json/no-unsafe-values": "error";
252
+ readonly "json/no-unnormalized-keys": "error";
253
+ };
254
+ export { rules_1 as rules };
255
+ }
256
+ }
207
257
  }
208
258
  import { TextSourceCodeBase } from '@eslint/plugin-kit';
209
259
  import { Directive } from '@eslint/plugin-kit';
@@ -174,7 +174,7 @@ declare namespace plugin {
174
174
  let rules: {
175
175
  "no-duplicate-keys": {
176
176
  meta: {
177
- type: string;
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: string;
193
+ type: "problem";
194
194
  docs: {
195
195
  description: string;
196
196
  };
@@ -202,8 +202,58 @@ declare namespace plugin {
202
202
  Member(node: any): void;
203
203
  };
204
204
  };
205
+ "no-unsafe-values": {
206
+ meta: {
207
+ type: "problem";
208
+ docs: {
209
+ description: string;
210
+ };
211
+ messages: {
212
+ unsafeNumber: string;
213
+ loneSurrogate: string;
214
+ };
215
+ };
216
+ create(context: any): {
217
+ Number(node: any): void;
218
+ String(node: any): void;
219
+ };
220
+ };
221
+ "no-unnormalized-keys": {
222
+ meta: {
223
+ type: "problem";
224
+ docs: {
225
+ description: string;
226
+ };
227
+ messages: {
228
+ unnormalizedKey: string;
229
+ };
230
+ schema: {
231
+ type: string;
232
+ properties: {
233
+ form: {
234
+ enum: string[];
235
+ };
236
+ };
237
+ additionalProperties: boolean;
238
+ }[];
239
+ };
240
+ create(context: any): {
241
+ Member(node: any): void;
242
+ };
243
+ };
205
244
  };
206
- let configs: {};
245
+ namespace configs {
246
+ namespace recommended {
247
+ export let plugins: {};
248
+ let rules_1: {
249
+ readonly "json/no-duplicate-keys": "error";
250
+ readonly "json/no-empty-keys": "error";
251
+ readonly "json/no-unsafe-values": "error";
252
+ readonly "json/no-unnormalized-keys": "error";
253
+ };
254
+ export { rules_1 as rules };
255
+ }
256
+ }
207
257
  }
208
258
  import { TextSourceCodeBase } from '@eslint/plugin-kit';
209
259
  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",
@@ -535,6 +535,122 @@ var noEmptyKeys = {
535
535
  },
536
536
  };
537
537
 
538
+ /**
539
+ * @fileoverview Rule to detect unsafe values in JSON.
540
+ * @author Bradley Meck Farias
541
+ */
542
+
543
+ var noUnsafeValues = {
544
+ meta: {
545
+ type: /** @type {const} */ ("problem"),
546
+
547
+ docs: {
548
+ description: "Disallow JSON values that are unsafe for interchange",
549
+ },
550
+
551
+ messages: {
552
+ unsafeNumber: "Number outside safe range found.",
553
+ loneSurrogate: "Lone surrogate '{{ surrogate }}' found.",
554
+ },
555
+ },
556
+
557
+ create(context) {
558
+ return {
559
+ Number(node) {
560
+ if (Number.isFinite(node.value) !== true) {
561
+ context.report({
562
+ loc: node.loc,
563
+ messageId: "unsafeNumber",
564
+ });
565
+ }
566
+ },
567
+ String(node) {
568
+ if (node.value.isWellFormed) {
569
+ if (node.value.isWellFormed()) {
570
+ return;
571
+ }
572
+ }
573
+ // match any high surrogate and, if it exists, a paired low surrogate
574
+ // match any low surrogate not already matched
575
+ const surrogatePattern =
576
+ /[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/gu;
577
+ let match = surrogatePattern.exec(node.value);
578
+ while (match) {
579
+ // only need to report non-paired surrogates
580
+ if (match[0].length < 2) {
581
+ context.report({
582
+ loc: node.loc,
583
+ messageId: "loneSurrogate",
584
+ data: {
585
+ surrogate: JSON.stringify(match[0]).slice(
586
+ 1,
587
+ -1,
588
+ ),
589
+ },
590
+ });
591
+ }
592
+ match = surrogatePattern.exec(node.value);
593
+ }
594
+ },
595
+ };
596
+ },
597
+ };
598
+
599
+ /**
600
+ * @fileoverview Rule to detect unnormalized keys in JSON.
601
+ * @author Bradley Meck Farias
602
+ */
603
+
604
+ var noUnnormalizedKeys = {
605
+ meta: {
606
+ type: /** @type {const} */ ("problem"),
607
+
608
+ docs: {
609
+ description: "Disallow JSON keys that are not normalized",
610
+ },
611
+
612
+ messages: {
613
+ unnormalizedKey: "Unnormalized key '{{key}}' found.",
614
+ },
615
+
616
+ schema: [
617
+ {
618
+ type: "object",
619
+ properties: {
620
+ form: {
621
+ enum: ["NFC", "NFD", "NFKC", "NFKD"],
622
+ },
623
+ },
624
+ additionalProperties: false,
625
+ },
626
+ ],
627
+ },
628
+
629
+ create(context) {
630
+ const normalization = context.options.length
631
+ ? text => text.normalize(context.options[0].form)
632
+ : text => text.normalize();
633
+ return {
634
+ Member(node) {
635
+ const key =
636
+ node.name.type === "String"
637
+ ? node.name.value
638
+ : node.name.name;
639
+
640
+ if (normalization(key) !== key) {
641
+ context.report({
642
+ loc: node.name.loc,
643
+ messageId: "unnormalizedKey",
644
+ data: {
645
+ key,
646
+ },
647
+ });
648
+ }
649
+ },
650
+ };
651
+ },
652
+ };
653
+
538
654
  /**
539
655
  * @fileoverview JSON plugin.
540
656
  * @author Nicholas C. Zakas
@@ -548,7 +664,7 @@ var noEmptyKeys = {
548
664
  const plugin = {
549
665
  meta: {
550
666
  name: "@eslint/json",
551
- version: "0.6.0", // x-release-please-version
667
+ version: "0.8.0", // x-release-please-version
552
668
  },
553
669
  languages: {
554
670
  json: new JSONLanguage({ mode: "json" }),
@@ -558,18 +674,25 @@ const plugin = {
558
674
  rules: {
559
675
  "no-duplicate-keys": noDuplicateKeys,
560
676
  "no-empty-keys": noEmptyKeys,
677
+ "no-unsafe-values": noUnsafeValues,
678
+ "no-unnormalized-keys": noUnnormalizedKeys,
561
679
  },
562
- configs: {},
563
- };
564
-
565
- Object.assign(plugin.configs, {
566
- recommended: {
567
- plugins: { json: plugin },
568
- rules: {
569
- "json/no-duplicate-keys": "error",
570
- "json/no-empty-keys": "error",
680
+ configs: {
681
+ recommended: {
682
+ plugins: {},
683
+ rules: /** @type {const} */ ({
684
+ "json/no-duplicate-keys": "error",
685
+ "json/no-empty-keys": "error",
686
+ "json/no-unsafe-values": "error",
687
+ "json/no-unnormalized-keys": "error",
688
+ }),
571
689
  },
572
690
  },
573
- });
691
+ };
692
+
693
+ // eslint-disable-next-line no-lone-blocks -- The block syntax { ... } ensures that TypeScript does not get confused about the type of `plugin`.
694
+ {
695
+ plugin.configs.recommended.plugins.json = plugin;
696
+ }
574
697
 
575
698
  export { JSONLanguage, JSONSourceCode, plugin as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint/json",
3
- "version": "0.6.0",
3
+ "version": "0.8.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",
@@ -62,8 +63,8 @@
62
63
  ],
63
64
  "license": "Apache-2.0",
64
65
  "dependencies": {
65
- "@eslint/plugin-kit": "^0.2.0",
66
- "@humanwhocodes/momoa": "^3.3.0"
66
+ "@eslint/plugin-kit": "^0.2.3",
67
+ "@humanwhocodes/momoa": "^3.3.3"
67
68
  },
68
69
  "devDependencies": {
69
70
  "@eslint/core": "^0.6.0",
@@ -72,6 +73,7 @@
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",