first commit
This commit is contained in:
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
# ESLint JSON Language Plugin
|
||||
|
||||
## Overview
|
||||
|
||||
This package contains a plugin that allows you to natively lint JSON, JSONC, and JSON5 files using ESLint.
|
||||
|
||||
**Important:** This plugin requires ESLint v9.15.0 or higher and you must be using the [new configuration system](https://eslint.org/docs/latest/use/configure/configuration-files).
|
||||
|
||||
## Installation
|
||||
|
||||
For Node.js and compatible runtimes:
|
||||
|
||||
```shell
|
||||
npm install @eslint/json -D
|
||||
# or
|
||||
yarn add @eslint/json -D
|
||||
# or
|
||||
pnpm install @eslint/json -D
|
||||
# or
|
||||
bun add @eslint/json -D
|
||||
```
|
||||
|
||||
For Deno:
|
||||
|
||||
```shell
|
||||
deno add @eslint/json
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
This package exports these languages:
|
||||
|
||||
- `"json/json"` is for regular JSON files
|
||||
- `"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](https://code.visualstudio.com/) configuration files
|
||||
- `"json/json5"` is for [JSON5](https://json5.org) files
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import { defineConfig } from "eslint/config";
|
||||
import json from "@eslint/json";
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
plugins: {
|
||||
json,
|
||||
},
|
||||
},
|
||||
|
||||
// lint JSON files
|
||||
{
|
||||
files: ["**/*.json"],
|
||||
language: "json/json",
|
||||
rules: {
|
||||
"json/no-duplicate-keys": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// lint JSONC files
|
||||
{
|
||||
files: ["**/*.jsonc", ".vscode/*.json"],
|
||||
language: "json/jsonc",
|
||||
rules: {
|
||||
"json/no-duplicate-keys": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// lint JSON5 files
|
||||
{
|
||||
files: ["**/*.json5"],
|
||||
language: "json/json5",
|
||||
rules: {
|
||||
"json/no-duplicate-keys": "error",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
In CommonJS format:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
const { defineConfig } = require("eslint/config");
|
||||
const json = require("@eslint/json").default;
|
||||
|
||||
module.exports = defineConfig([
|
||||
{
|
||||
plugins: {
|
||||
json,
|
||||
},
|
||||
},
|
||||
|
||||
// lint JSON files
|
||||
{
|
||||
files: ["**/*.json"],
|
||||
language: "json/json",
|
||||
rules: {
|
||||
"json/no-duplicate-keys": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// lint JSONC files
|
||||
{
|
||||
files: ["**/*.jsonc", ".vscode/*.json"],
|
||||
language: "json/jsonc",
|
||||
rules: {
|
||||
"json/no-duplicate-keys": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// lint JSON5 files
|
||||
{
|
||||
files: ["**/*.json5"],
|
||||
language: "json/json5",
|
||||
rules: {
|
||||
"json/no-duplicate-keys": "error",
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
## Recommended Configuration
|
||||
|
||||
To use the recommended configuration for this plugin, specify your matching `files` and then use the `extends: ["json/recommended"]` property, like this:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import { defineConfig } from "eslint/config";
|
||||
import json from "@eslint/json";
|
||||
|
||||
export default defineConfig([
|
||||
// lint JSON files
|
||||
{
|
||||
files: ["**/*.json"],
|
||||
ignores: ["package-lock.json"],
|
||||
plugins: { json },
|
||||
language: "json/json",
|
||||
extends: ["json/recommended"],
|
||||
},
|
||||
|
||||
// lint JSONC files
|
||||
{
|
||||
files: ["**/*.jsonc"],
|
||||
plugins: { json },
|
||||
language: "json/jsonc",
|
||||
extends: ["json/recommended"],
|
||||
},
|
||||
|
||||
// lint JSON5 files
|
||||
{
|
||||
files: ["**/*.json5"],
|
||||
plugins: { json },
|
||||
language: "json/json5",
|
||||
extends: ["json/recommended"],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
**Note:** You generally want to ignore `package-lock.json` because it is auto-generated and you typically will not want to manually make changes to it.
|
||||
|
||||
## Rules
|
||||
|
||||
<!-- NOTE: The following table is autogenerated. Do not manually edit. -->
|
||||
|
||||
<!-- Rule Table Start -->
|
||||
|
||||
| **Rule Name** | **Description** | **Recommended** |
|
||||
| :------------------------------------------------------------- | :-------------------------------------------------------- | :-------------: |
|
||||
| [`no-duplicate-keys`](./docs/rules/no-duplicate-keys.md) | Disallow duplicate keys in JSON objects | yes |
|
||||
| [`no-empty-keys`](./docs/rules/no-empty-keys.md) | Disallow empty keys in JSON objects | yes |
|
||||
| [`no-unnormalized-keys`](./docs/rules/no-unnormalized-keys.md) | Disallow JSON keys that are not normalized | yes |
|
||||
| [`no-unsafe-values`](./docs/rules/no-unsafe-values.md) | Disallow JSON values that are unsafe for interchange | yes |
|
||||
| [`sort-keys`](./docs/rules/sort-keys.md) | Require JSON object keys to be sorted | no |
|
||||
| [`top-level-interop`](./docs/rules/top-level-interop.md) | Require the JSON top-level value to be an array or object | no |
|
||||
|
||||
<!-- Rule Table End -->
|
||||
|
||||
## Configuration Comments
|
||||
|
||||
In JSONC and JSON5 files, you can also use [rule configurations comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments) and [disable directives](https://eslint.org/docs/latest/use/configure/rules#disabling-rules).
|
||||
|
||||
```jsonc
|
||||
/* eslint json/no-empty-keys: "error" */
|
||||
|
||||
{
|
||||
"foo": {
|
||||
"": 1, // eslint-disable-line json/no-empty-keys -- We want an empty key here
|
||||
},
|
||||
"bar": {
|
||||
// eslint-disable-next-line json/no-empty-keys -- We want an empty key here too
|
||||
"": 2,
|
||||
},
|
||||
/* eslint-disable json/no-empty-keys -- Empty keys are allowed in the following code as well */
|
||||
"baz": [
|
||||
{
|
||||
"": 3,
|
||||
},
|
||||
{
|
||||
"": 4,
|
||||
},
|
||||
],
|
||||
/* eslint-enable json/no-empty-keys -- re-enable now */
|
||||
}
|
||||
```
|
||||
|
||||
Both line and block comments can be used for all kinds of configuration comments.
|
||||
|
||||
## Allowing trailing commas in JSONC
|
||||
|
||||
The Microsoft implementation of JSONC optionally allows for trailing commas in objects and arrays (files like `tsconfig.json` have this option enabled by default in Visual Studio Code). To enable trailing commas in JSONC files, use the `allowTrailingCommas` language option, as in this example:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import { defineConfig } from "eslint/config";
|
||||
import json from "@eslint/json";
|
||||
|
||||
export default defineConfig([
|
||||
// lint JSONC files
|
||||
{
|
||||
files: ["**/*.jsonc"],
|
||||
plugins: { json },
|
||||
language: "json/jsonc",
|
||||
extends: ["json/recommended"],
|
||||
},
|
||||
|
||||
// lint JSONC files and allow trailing commas
|
||||
{
|
||||
files: ["**/tsconfig.json", ".vscode/*.json"],
|
||||
plugins: { json },
|
||||
language: "json/jsonc",
|
||||
languageOptions: {
|
||||
allowTrailingCommas: true,
|
||||
},
|
||||
extends: ["json/recommended"],
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
**Note:** The `allowTrailingCommas` option is only valid for the `json/jsonc` language.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How does this relate to `eslint-plugin-json` and `eslint-plugin-jsonc`?
|
||||
|
||||
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:
|
||||
|
||||
- [`eslint-plugin-json`](https://github.com/azeemba/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.
|
||||
- [`eslint-plugin-jsonc`](https://github.com/ota-meshi/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.
|
||||
|
||||
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.
|
||||
|
||||
### What about missing rules that are available in `eslint-plugin-json` and `eslint-plugin-jsonc`?
|
||||
|
||||
Most of the rules in `eslint-plugin-json` are actually syntax errors that are caught automatically by the parser used in this plugin.
|
||||
|
||||
Similarly, many of the rules in `eslint-plugin-jsonc` specifically disallow valid JavaScript syntax that is invalid in the context of JSON. These are also automatically caught by the parser in this plugin.
|
||||
|
||||
Any other rules that catch potential problems in JSON are welcome to be implemented. You can [open an issue](https://github.com/eslint/json/issues/new/choose) to propose a new rule.
|
||||
|
||||
## Editor and IDE Setup
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
First, ensure you have the [ESLint plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) installed.
|
||||
|
||||
Then, edit `eslint.validate` in your `settings.json` file to include `json`, `jsonc`, and `json5`:
|
||||
|
||||
```json
|
||||
{
|
||||
"eslint.validate": ["json", "jsonc", "json5"]
|
||||
}
|
||||
```
|
||||
|
||||
### JetBrains WebStorm
|
||||
|
||||
For any [JetBrains WebStorm](https://www.jetbrains.com/webstorm/), configure the [ESLint scope](https://www.jetbrains.com/help/webstorm/eslint.html#ws_eslint_configure_scope) to include `json`, `jsonc`, and `json5`, such as:
|
||||
|
||||
```text
|
||||
**/*.{js,ts,jsx,tsx,cjs,cts,mjs,mts,html,vue,json,jsonc,json5}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0
|
||||
|
||||
<!-- NOTE: This section is autogenerated. Do not manually edit.-->
|
||||
<!--sponsorsstart-->
|
||||
|
||||
## Sponsors
|
||||
|
||||
The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate)
|
||||
to get your logo on our READMEs and [website](https://eslint.org/sponsors).
|
||||
|
||||
<h3>Platinum Sponsors</h3>
|
||||
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a></p><h3>Gold Sponsors</h3>
|
||||
<p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a> <a href="https://shopify.engineering/"><img src="https://avatars.githubusercontent.com/u/8085" alt="Shopify" height="96"></a> <a href="https://www.coderabbit.ai/?utm_source=cr_org&utm_medium=github"><img src="https://avatars.githubusercontent.com/u/132028505" alt="CodeRabbit" height="96"></a></p><h3>Silver Sponsors</h3>
|
||||
<p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/d472863/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/2d6c3b6/logo.png" alt="Liftoff" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
|
||||
<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://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" 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" alt="GitBook" height="32"></a> <a href="https://citadel.co.jp"><img src="https://avatars.githubusercontent.com/u/75781367" alt="Citadel AI" height="32"></a></p>
|
||||
<h3>Technology Sponsors</h3>
|
||||
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.
|
||||
<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>
|
||||
<!--sponsorsend-->
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export default rules;
|
||||
declare const rules: {
|
||||
readonly "json/no-duplicate-keys": "error";
|
||||
readonly "json/no-empty-keys": "error";
|
||||
readonly "json/no-unnormalized-keys": "error";
|
||||
readonly "json/no-unsafe-values": "error";
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
const rules = /** @type {const} */ ({
|
||||
"json/no-duplicate-keys": "error",
|
||||
"json/no-empty-keys": "error",
|
||||
"json/no-unnormalized-keys": "error",
|
||||
"json/no-unsafe-values": "error"
|
||||
});
|
||||
export default rules;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
declare const _default: {
|
||||
"no-duplicate-keys": import("../rules/no-duplicate-keys.js").NoDuplicateKeysRuleDefinition;
|
||||
"no-empty-keys": import("../rules/no-empty-keys.js").NoEmptyKeysRuleDefinition;
|
||||
"no-unnormalized-keys": import("../rules/no-unnormalized-keys.js").NoUnnormalizedKeysRuleDefinition;
|
||||
"no-unsafe-values": import("../rules/no-unsafe-values.js").NoUnsafeValuesRuleDefinition;
|
||||
"sort-keys": import("../rules/sort-keys.js").SortKeysRuleDefinition;
|
||||
"top-level-interop": import("../rules/top-level-interop.js").TopLevelInteropRuleDefinition;
|
||||
};
|
||||
export default _default;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import rule0 from "../rules/no-duplicate-keys.js";
|
||||
import rule1 from "../rules/no-empty-keys.js";
|
||||
import rule2 from "../rules/no-unnormalized-keys.js";
|
||||
import rule3 from "../rules/no-unsafe-values.js";
|
||||
import rule4 from "../rules/sort-keys.js";
|
||||
import rule5 from "../rules/top-level-interop.js";
|
||||
export default {
|
||||
"no-duplicate-keys": rule0,
|
||||
"no-empty-keys": rule1,
|
||||
"no-unnormalized-keys": rule2,
|
||||
"no-unsafe-values": rule3,
|
||||
"sort-keys": rule4,
|
||||
"top-level-interop": rule5,
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
export default plugin;
|
||||
export { JSONSourceCode };
|
||||
export * from "./languages/json-language.js";
|
||||
export * from "./types.js";
|
||||
declare namespace plugin {
|
||||
export namespace meta {
|
||||
let name: string;
|
||||
let namespace: string;
|
||||
let version: string;
|
||||
}
|
||||
export namespace languages {
|
||||
let json: JSONLanguage;
|
||||
let jsonc: JSONLanguage;
|
||||
let json5: JSONLanguage;
|
||||
}
|
||||
export { rules };
|
||||
export namespace configs {
|
||||
namespace recommended {
|
||||
let name_1: string;
|
||||
export { name_1 as name };
|
||||
export let plugins: {};
|
||||
export { recommendedRules as rules };
|
||||
}
|
||||
}
|
||||
}
|
||||
import { JSONSourceCode } from "./languages/json-source-code.js";
|
||||
import { JSONLanguage } from "./languages/json-language.js";
|
||||
import rules from "./build/rules.js";
|
||||
import recommendedRules from "./build/recommended-config.js";
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @fileoverview JSON plugin.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Imports
|
||||
//-----------------------------------------------------------------------------
|
||||
import { JSONLanguage } from "./languages/json-language.js";
|
||||
import { JSONSourceCode } from "./languages/json-source-code.js";
|
||||
import recommendedRules from "./build/recommended-config.js";
|
||||
import rules from "./build/rules.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Plugin
|
||||
//-----------------------------------------------------------------------------
|
||||
const plugin = {
|
||||
meta: {
|
||||
name: "@eslint/json",
|
||||
namespace: "json",
|
||||
version: "2.0.1", // x-release-please-version
|
||||
},
|
||||
languages: {
|
||||
json: new JSONLanguage({ mode: "json" }),
|
||||
jsonc: new JSONLanguage({ mode: "jsonc" }),
|
||||
json5: new JSONLanguage({ mode: "json5" }),
|
||||
},
|
||||
rules,
|
||||
configs: {
|
||||
recommended: {
|
||||
name: "@eslint/json/recommended",
|
||||
plugins: {},
|
||||
rules: recommendedRules,
|
||||
},
|
||||
},
|
||||
};
|
||||
Object.assign(plugin.configs.recommended.plugins, { json: plugin });
|
||||
export default plugin;
|
||||
export { JSONSourceCode };
|
||||
export * from "./languages/json-language.js";
|
||||
export * from "./types.js";
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
/**
|
||||
* JSON Language Object
|
||||
* @implements {Language<{ LangOptions: JSONLanguageOptions; Code: JSONSourceCode; RootNode: DocumentNode; Node: AnyNode }>}
|
||||
*/
|
||||
export class JSONLanguage implements Language {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options to use for this instance.
|
||||
* @param {"json"|"jsonc"|"json5"} options.mode The parser mode to use.
|
||||
*/
|
||||
constructor({ mode }: {
|
||||
mode: "json" | "jsonc" | "json5";
|
||||
});
|
||||
/**
|
||||
* The type of file to read.
|
||||
* @type {"text"}
|
||||
*/
|
||||
fileType: "text";
|
||||
/**
|
||||
* The line number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
lineStart: 0 | 1;
|
||||
/**
|
||||
* The column number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
columnStart: 0 | 1;
|
||||
/**
|
||||
* The name of the key that holds the type of the node.
|
||||
* @type {string}
|
||||
*/
|
||||
nodeTypeKey: string;
|
||||
/**
|
||||
* The visitor keys.
|
||||
* @type {Record<string, string[]>}
|
||||
*/
|
||||
visitorKeys: Record<string, string[]>;
|
||||
/**
|
||||
* Validates the language options.
|
||||
* @param {JSONLanguageOptions} languageOptions The language options to validate.
|
||||
* @returns {void}
|
||||
* @throws {Error} When the language options are invalid.
|
||||
*/
|
||||
validateLanguageOptions(languageOptions: JSONLanguageOptions): void;
|
||||
/**
|
||||
* Parses the given file into an AST.
|
||||
* @param {File} file The virtual file to parse.
|
||||
* @param {{languageOptions: JSONLanguageOptions}} context The options to use for parsing.
|
||||
* @returns {JSONParseResult} The result of parsing.
|
||||
*/
|
||||
parse(file: File, context: {
|
||||
languageOptions: JSONLanguageOptions;
|
||||
}): JSONParseResult;
|
||||
/**
|
||||
* Creates a new `JSONSourceCode` object from the given information.
|
||||
* @param {File} file The virtual file to create a `JSONSourceCode` object from.
|
||||
* @param {JSONOkParseResult} parseResult The result returned from `parse()`.
|
||||
* @returns {JSONSourceCode} The new `JSONSourceCode` object.
|
||||
*/
|
||||
createSourceCode(file: File, parseResult: JSONOkParseResult): JSONSourceCode;
|
||||
#private;
|
||||
}
|
||||
export type JSONOkParseResult = OkParseResult<DocumentNode>;
|
||||
export type JSONParseResult = ParseResult<DocumentNode>;
|
||||
export type JSONLanguageOptions = {
|
||||
/**
|
||||
* Whether to allow trailing commas in JSONC mode.
|
||||
*/
|
||||
allowTrailingCommas?: boolean;
|
||||
};
|
||||
import type { Language } from "@eslint/core";
|
||||
import type { File } from "@eslint/core";
|
||||
import { JSONSourceCode } from "./json-source-code.js";
|
||||
import type { DocumentNode } from "@humanwhocodes/momoa";
|
||||
import type { OkParseResult } from "@eslint/core";
|
||||
import type { ParseResult } from "@eslint/core";
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* @fileoverview The JSONLanguage class.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
// Imports
|
||||
//------------------------------------------------------------------------------
|
||||
import { parse } from "@humanwhocodes/momoa";
|
||||
import { JSONSourceCode } from "./json-source-code.js";
|
||||
import { visitorKeys } from "@humanwhocodes/momoa";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* JSON Language Object
|
||||
* @implements {Language<{ LangOptions: JSONLanguageOptions; Code: JSONSourceCode; RootNode: DocumentNode; Node: AnyNode }>}
|
||||
*/
|
||||
export class JSONLanguage {
|
||||
/**
|
||||
* The type of file to read.
|
||||
* @type {"text"}
|
||||
*/
|
||||
fileType = "text";
|
||||
/**
|
||||
* The line number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
lineStart = 1;
|
||||
/**
|
||||
* The column number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
columnStart = 1;
|
||||
/**
|
||||
* The name of the key that holds the type of the node.
|
||||
* @type {string}
|
||||
*/
|
||||
nodeTypeKey = "type";
|
||||
/**
|
||||
* The parser mode.
|
||||
* @type {"json"|"jsonc"|"json5"}
|
||||
*/
|
||||
#mode = "json";
|
||||
/**
|
||||
* The visitor keys.
|
||||
* @type {Record<string, string[]>}
|
||||
*/
|
||||
visitorKeys = Object.fromEntries([...visitorKeys]);
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options to use for this instance.
|
||||
* @param {"json"|"jsonc"|"json5"} options.mode The parser mode to use.
|
||||
*/
|
||||
constructor({ mode }) {
|
||||
this.#mode = mode;
|
||||
}
|
||||
/**
|
||||
* Validates the language options.
|
||||
* @param {JSONLanguageOptions} languageOptions The language options to validate.
|
||||
* @returns {void}
|
||||
* @throws {Error} When the language options are invalid.
|
||||
*/
|
||||
validateLanguageOptions(languageOptions) {
|
||||
if (languageOptions.allowTrailingCommas !== undefined) {
|
||||
if (typeof languageOptions.allowTrailingCommas !== "boolean") {
|
||||
throw new Error("allowTrailingCommas must be a boolean if provided.");
|
||||
}
|
||||
// we know that allowTrailingCommas is a boolean here
|
||||
// only allowed in JSONC mode
|
||||
if (this.#mode !== "jsonc") {
|
||||
throw new Error("allowTrailingCommas option is only available in JSONC.");
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parses the given file into an AST.
|
||||
* @param {File} file The virtual file to parse.
|
||||
* @param {{languageOptions: JSONLanguageOptions}} context The options to use for parsing.
|
||||
* @returns {JSONParseResult} The result of parsing.
|
||||
*/
|
||||
parse(file, context) {
|
||||
// Note: BOM already removed
|
||||
const text = /** @type {string} */ (file.body);
|
||||
const allowTrailingCommas = context?.languageOptions?.allowTrailingCommas;
|
||||
/*
|
||||
* Check for parsing errors first. If there's a parsing error, nothing
|
||||
* else can happen. However, a parsing error does not throw an error
|
||||
* from this method - it's just considered a fatal error message, a
|
||||
* problem that ESLint identified just like any other.
|
||||
*/
|
||||
try {
|
||||
const root = parse(text, {
|
||||
mode: this.#mode,
|
||||
ranges: true,
|
||||
tokens: true,
|
||||
allowTrailingCommas,
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
ast: root,
|
||||
};
|
||||
}
|
||||
catch (ex) {
|
||||
// error messages end with (line:column) so we strip that off for ESLint
|
||||
const message = ex.message
|
||||
.slice(0, ex.message.lastIndexOf("("))
|
||||
.trim();
|
||||
return {
|
||||
ok: false,
|
||||
errors: [
|
||||
{
|
||||
...ex,
|
||||
message,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
/* eslint-disable class-methods-use-this -- Required to complete interface. */
|
||||
/**
|
||||
* Creates a new `JSONSourceCode` object from the given information.
|
||||
* @param {File} file The virtual file to create a `JSONSourceCode` object from.
|
||||
* @param {JSONOkParseResult} parseResult The result returned from `parse()`.
|
||||
* @returns {JSONSourceCode} The new `JSONSourceCode` object.
|
||||
*/
|
||||
createSourceCode(file, parseResult) {
|
||||
return new JSONSourceCode({
|
||||
text: /** @type {string} */ (file.body),
|
||||
ast: parseResult.ast,
|
||||
});
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* JSON Source Code Object
|
||||
* @extends {TextSourceCodeBase<{LangOptions: JSONLanguageOptions, RootNode: DocumentNode, SyntaxElementWithLoc: JSONSyntaxElement, ConfigNode: Token}>}
|
||||
*/
|
||||
export class JSONSourceCode extends TextSourceCodeBase<{
|
||||
LangOptions: JSONLanguageOptions;
|
||||
RootNode: DocumentNode;
|
||||
SyntaxElementWithLoc: JSONSyntaxElement;
|
||||
ConfigNode: Token;
|
||||
}> {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the instance.
|
||||
* @param {string} options.text The source code text.
|
||||
* @param {DocumentNode} options.ast The root AST node.
|
||||
*/
|
||||
constructor({ text, ast }: {
|
||||
text: string;
|
||||
ast: DocumentNode;
|
||||
});
|
||||
/**
|
||||
* The comment tokens in the source code.
|
||||
* @type {Array<Token>|undefined}
|
||||
*/
|
||||
comments: Array<Token> | undefined;
|
||||
/**
|
||||
* Returns an array of all inline configuration nodes found in the
|
||||
* source code.
|
||||
* @returns {Array<Token>} An array of all inline configuration nodes.
|
||||
*/
|
||||
getInlineConfigNodes(): Array<Token>;
|
||||
/**
|
||||
* Returns directives that enable or disable rules along with any problems
|
||||
* encountered while parsing the directives.
|
||||
* @returns {{problems:Array<FileProblem>,directives:Array<Directive>}} Information
|
||||
* that ESLint needs to further process the directives.
|
||||
*/
|
||||
getDisableDirectives(): {
|
||||
problems: Array<FileProblem>;
|
||||
directives: Array<Directive>;
|
||||
};
|
||||
/**
|
||||
* Returns inline rule configurations along with any problems
|
||||
* encountered while parsing the configurations.
|
||||
* @returns {{problems:Array<FileProblem>,configs:Array<{config:{rules:RulesConfig},loc:LocationRange}>}} Information
|
||||
* that ESLint needs to further process the rule configurations.
|
||||
*/
|
||||
applyInlineConfig(): {
|
||||
problems: Array<FileProblem>;
|
||||
configs: Array<{
|
||||
config: {
|
||||
rules: RulesConfig;
|
||||
};
|
||||
loc: LocationRange;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* Returns the parent of the given node.
|
||||
* @param {AnyNode} node The node to get the parent of.
|
||||
* @returns {AnyNode|undefined} The parent of the node.
|
||||
*/
|
||||
getParent(node: AnyNode): AnyNode | undefined;
|
||||
/**
|
||||
* Traverse the source code and return the steps that were taken.
|
||||
* @returns {Iterable<JSONTraversalStep>} The steps that were taken while traversing the source code.
|
||||
*/
|
||||
traverse(): Iterable<JSONTraversalStep>;
|
||||
/**
|
||||
* Gets the token before the given node or token, optionally including comments.
|
||||
* @param {AnyNode|Token} nodeOrToken The node or token to get the previous token for.
|
||||
* @param {Object} [options] Options object.
|
||||
* @param {boolean} [options.includeComments] If true, return comments when they are present.
|
||||
* @returns {Token|null} The previous token or comment, or null if there is none.
|
||||
*/
|
||||
getTokenBefore(nodeOrToken: AnyNode | Token, { includeComments }?: {
|
||||
includeComments?: boolean;
|
||||
}): Token | null;
|
||||
/**
|
||||
* Gets the token after the given node or token, skipping any comments unless includeComments is true.
|
||||
* @param {AnyNode|Token} nodeOrToken The node or token to get the next token for.
|
||||
* @param {Object} [options] Options object.
|
||||
* @param {boolean} [options.includeComments=false] If true, return comments when they are present.
|
||||
* @returns {Token|null} The next token or comment, or null if there is none.
|
||||
*/
|
||||
getTokenAfter(nodeOrToken: AnyNode | Token, { includeComments }?: {
|
||||
includeComments?: boolean;
|
||||
}): Token | null;
|
||||
#private;
|
||||
}
|
||||
import type { JSONLanguageOptions } from "./json-language.js";
|
||||
import type { DocumentNode } from "@humanwhocodes/momoa";
|
||||
import type { JSONSyntaxElement } from "../types.js";
|
||||
import type { Token } from "@humanwhocodes/momoa";
|
||||
import { TextSourceCodeBase } from "@eslint/plugin-kit";
|
||||
import type { FileProblem } from "@eslint/core";
|
||||
import { Directive } from "@eslint/plugin-kit";
|
||||
import type { RulesConfig } from "@eslint/core";
|
||||
import type { LocationRange } from "@humanwhocodes/momoa";
|
||||
import type { AnyNode } from "@humanwhocodes/momoa";
|
||||
/**
|
||||
* A class to represent a step in the traversal process.
|
||||
*/
|
||||
declare class JSONTraversalStep extends VisitNodeStep {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the step.
|
||||
* @param {AnyNode} options.target The target of the step.
|
||||
* @param {1|2} options.phase The phase of the step.
|
||||
* @param {Array<any>} options.args The arguments of the step.
|
||||
*/
|
||||
constructor({ target, phase, args }: {
|
||||
target: AnyNode;
|
||||
phase: 1 | 2;
|
||||
args: Array<any>;
|
||||
});
|
||||
/**
|
||||
* The target of the step.
|
||||
* @type {AnyNode}
|
||||
*/
|
||||
target: AnyNode;
|
||||
}
|
||||
import { VisitNodeStep } from "@eslint/plugin-kit";
|
||||
export {};
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* @fileoverview The JSONSourceCode class.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Imports
|
||||
//-----------------------------------------------------------------------------
|
||||
import { iterator } from "@humanwhocodes/momoa";
|
||||
import { VisitNodeStep, TextSourceCodeBase, ConfigCommentParser, Directive, } from "@eslint/plugin-kit";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { DocumentNode, AnyNode, Token, LocationRange } from "@humanwhocodes/momoa";
|
||||
* @import { FileProblem, DirectiveType, RulesConfig } from "@eslint/core";
|
||||
* @import { JSONSyntaxElement } from "../types.js";
|
||||
* @import { JSONLanguageOptions } from "./json-language.js";
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
const commentParser = new ConfigCommentParser();
|
||||
const INLINE_CONFIG = /^\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|$)/u;
|
||||
/**
|
||||
* A class to represent a step in the traversal process.
|
||||
*/
|
||||
class JSONTraversalStep extends VisitNodeStep {
|
||||
/**
|
||||
* The target of the step.
|
||||
* @type {AnyNode}
|
||||
*/
|
||||
target = undefined;
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the step.
|
||||
* @param {AnyNode} options.target The target of the step.
|
||||
* @param {1|2} options.phase The phase of the step.
|
||||
* @param {Array<any>} options.args The arguments of the step.
|
||||
*/
|
||||
constructor({ target, phase, args }) {
|
||||
super({ target, phase, args });
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Processes tokens to extract comments and their starting tokens.
|
||||
* @param {Array<Token>} tokens The tokens to process.
|
||||
* @returns {{ comments: Array<Token>, starts: Map<number, number>, ends: Map<number, number>}}
|
||||
* An object containing an array of comments, a map of starting token range to token index, and
|
||||
* a map of ending token range to token index.
|
||||
*/
|
||||
function processTokens(tokens) {
|
||||
/** @type {Array<Token>} */
|
||||
const comments = [];
|
||||
/** @type {Map<number, number>} */
|
||||
const starts = new Map();
|
||||
/** @type {Map<number, number>} */
|
||||
const ends = new Map();
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const token = tokens[i];
|
||||
if (token.type.endsWith("Comment")) {
|
||||
comments.push(token);
|
||||
}
|
||||
starts.set(token.range[0], i);
|
||||
ends.set(token.range[1], i);
|
||||
}
|
||||
return { comments, starts, ends };
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* JSON Source Code Object
|
||||
* @extends {TextSourceCodeBase<{LangOptions: JSONLanguageOptions, RootNode: DocumentNode, SyntaxElementWithLoc: JSONSyntaxElement, ConfigNode: Token}>}
|
||||
*/
|
||||
export class JSONSourceCode extends TextSourceCodeBase {
|
||||
/**
|
||||
* Cached traversal steps.
|
||||
* @type {Array<JSONTraversalStep>|undefined}
|
||||
*/
|
||||
#steps;
|
||||
/**
|
||||
* Cache of parent nodes.
|
||||
* @type {WeakMap<AnyNode, AnyNode>}
|
||||
*/
|
||||
#parents = new WeakMap();
|
||||
/**
|
||||
* Collection of inline configuration comments.
|
||||
* @type {Array<Token>}
|
||||
*/
|
||||
#inlineConfigComments;
|
||||
/**
|
||||
* The AST of the source code.
|
||||
* @type {DocumentNode}
|
||||
*/
|
||||
ast = undefined;
|
||||
/**
|
||||
* The comment tokens in the source code.
|
||||
* @type {Array<Token>|undefined}
|
||||
*/
|
||||
comments;
|
||||
/**
|
||||
* A map of token start positions to their corresponding index.
|
||||
* @type {Map<number, number>}
|
||||
*/
|
||||
#tokenStarts;
|
||||
/**
|
||||
* A map of token end positions to their corresponding index.
|
||||
* @type {Map<number, number>}
|
||||
*/
|
||||
#tokenEnds;
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the instance.
|
||||
* @param {string} options.text The source code text.
|
||||
* @param {DocumentNode} options.ast The root AST node.
|
||||
*/
|
||||
constructor({ text, ast }) {
|
||||
super({ text, ast, lineEndingPattern: /\r\n|[\r\n]/u });
|
||||
this.ast = ast;
|
||||
const { comments, starts, ends } = processTokens(this.ast.tokens ?? []);
|
||||
this.comments = comments;
|
||||
this.#tokenStarts = starts;
|
||||
this.#tokenEnds = ends;
|
||||
}
|
||||
/**
|
||||
* Returns the value of the given comment.
|
||||
* @param {Token} comment The comment to get the value of.
|
||||
* @returns {string} The value of the comment.
|
||||
* @throws {Error} When an unexpected comment type is passed.
|
||||
*/
|
||||
#getCommentValue(comment) {
|
||||
if (comment.type === "LineComment") {
|
||||
return this.getText(comment).slice(2); // strip leading `//`
|
||||
}
|
||||
if (comment.type === "BlockComment") {
|
||||
return this.getText(comment).slice(2, -2); // strip leading `/*` and trailing `*/`
|
||||
}
|
||||
throw new Error(`Unexpected comment type '${comment.type}'`);
|
||||
}
|
||||
/**
|
||||
* Returns an array of all inline configuration nodes found in the
|
||||
* source code.
|
||||
* @returns {Array<Token>} An array of all inline configuration nodes.
|
||||
*/
|
||||
getInlineConfigNodes() {
|
||||
if (!this.#inlineConfigComments) {
|
||||
this.#inlineConfigComments = this.comments.filter(comment => INLINE_CONFIG.test(this.#getCommentValue(comment)));
|
||||
}
|
||||
return this.#inlineConfigComments ?? [];
|
||||
}
|
||||
/**
|
||||
* Returns directives that enable or disable rules along with any problems
|
||||
* encountered while parsing the directives.
|
||||
* @returns {{problems:Array<FileProblem>,directives:Array<Directive>}} Information
|
||||
* that ESLint needs to further process the directives.
|
||||
*/
|
||||
getDisableDirectives() {
|
||||
/** @type {Array<FileProblem>} */
|
||||
const problems = [];
|
||||
/** @type {Array<Directive>} */
|
||||
const directives = [];
|
||||
this.getInlineConfigNodes().forEach(comment => {
|
||||
const { label, value, justification } = commentParser.parseDirective(this.#getCommentValue(comment));
|
||||
// `eslint-disable-line` directives are not allowed to span multiple lines as it would be confusing to which lines they apply
|
||||
if (label === "eslint-disable-line" &&
|
||||
comment.loc.start.line !== comment.loc.end.line) {
|
||||
const message = `${label} comment should not span multiple lines.`;
|
||||
problems.push({
|
||||
ruleId: null,
|
||||
message,
|
||||
loc: comment.loc,
|
||||
});
|
||||
return;
|
||||
}
|
||||
switch (label) {
|
||||
case "eslint-disable":
|
||||
case "eslint-enable":
|
||||
case "eslint-disable-next-line":
|
||||
case "eslint-disable-line": {
|
||||
const directiveType = label.slice("eslint-".length);
|
||||
directives.push(new Directive({
|
||||
type: /** @type {DirectiveType} */ (directiveType),
|
||||
node: comment,
|
||||
value,
|
||||
justification,
|
||||
}));
|
||||
}
|
||||
// no default
|
||||
}
|
||||
});
|
||||
return { problems, directives };
|
||||
}
|
||||
/**
|
||||
* Returns inline rule configurations along with any problems
|
||||
* encountered while parsing the configurations.
|
||||
* @returns {{problems:Array<FileProblem>,configs:Array<{config:{rules:RulesConfig},loc:LocationRange}>}} Information
|
||||
* that ESLint needs to further process the rule configurations.
|
||||
*/
|
||||
applyInlineConfig() {
|
||||
/** @type {Array<FileProblem>} */
|
||||
const problems = [];
|
||||
/** @type {Array<{config:{rules:RulesConfig},loc:LocationRange}>} */
|
||||
const configs = [];
|
||||
this.getInlineConfigNodes().forEach(comment => {
|
||||
const { label, value } = commentParser.parseDirective(this.#getCommentValue(comment));
|
||||
if (label === "eslint") {
|
||||
const parseResult = commentParser.parseJSONLikeConfig(value);
|
||||
if (parseResult.ok) {
|
||||
configs.push({
|
||||
config: {
|
||||
rules: parseResult.config,
|
||||
},
|
||||
loc: comment.loc,
|
||||
});
|
||||
}
|
||||
else {
|
||||
problems.push({
|
||||
ruleId: null,
|
||||
message:
|
||||
/** @type {{ok: false, error: { message: string }}} */ (parseResult).error.message,
|
||||
loc: comment.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
configs,
|
||||
problems,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Returns the parent of the given node.
|
||||
* @param {AnyNode} node The node to get the parent of.
|
||||
* @returns {AnyNode|undefined} The parent of the node.
|
||||
*/
|
||||
getParent(node) {
|
||||
return this.#parents.get(node);
|
||||
}
|
||||
/**
|
||||
* Traverse the source code and return the steps that were taken.
|
||||
* @returns {Iterable<JSONTraversalStep>} The steps that were taken while traversing the source code.
|
||||
*/
|
||||
traverse() {
|
||||
// Because the AST doesn't mutate, we can cache the steps
|
||||
if (this.#steps) {
|
||||
return this.#steps.values();
|
||||
}
|
||||
/** @type {Array<JSONTraversalStep>} */
|
||||
const steps = (this.#steps = []);
|
||||
for (const { node, parent, phase } of iterator(this.ast)) {
|
||||
if (parent) {
|
||||
this.#parents.set(
|
||||
/** @type {AnyNode} */ (node),
|
||||
/** @type {AnyNode} */ (parent));
|
||||
}
|
||||
steps.push(new JSONTraversalStep({
|
||||
target: /** @type {AnyNode} */ (node),
|
||||
phase: phase === "enter" ? 1 : 2,
|
||||
args: [node, parent],
|
||||
}));
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
/**
|
||||
* Gets the token before the given node or token, optionally including comments.
|
||||
* @param {AnyNode|Token} nodeOrToken The node or token to get the previous token for.
|
||||
* @param {Object} [options] Options object.
|
||||
* @param {boolean} [options.includeComments] If true, return comments when they are present.
|
||||
* @returns {Token|null} The previous token or comment, or null if there is none.
|
||||
*/
|
||||
getTokenBefore(nodeOrToken, { includeComments = false } = {}) {
|
||||
const index = this.#tokenStarts.get(nodeOrToken.range[0]);
|
||||
if (index === undefined) {
|
||||
return null;
|
||||
}
|
||||
let previousIndex = index - 1;
|
||||
if (previousIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
const tokens = this.ast.tokens;
|
||||
let tokenOrComment = tokens[previousIndex];
|
||||
if (includeComments) {
|
||||
return tokenOrComment;
|
||||
}
|
||||
// skip comments
|
||||
while (tokenOrComment?.type.endsWith("Comment")) {
|
||||
previousIndex--;
|
||||
if (previousIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
tokenOrComment = tokens[previousIndex];
|
||||
}
|
||||
return tokenOrComment;
|
||||
}
|
||||
/**
|
||||
* Gets the token after the given node or token, skipping any comments unless includeComments is true.
|
||||
* @param {AnyNode|Token} nodeOrToken The node or token to get the next token for.
|
||||
* @param {Object} [options] Options object.
|
||||
* @param {boolean} [options.includeComments=false] If true, return comments when they are present.
|
||||
* @returns {Token|null} The next token or comment, or null if there is none.
|
||||
*/
|
||||
getTokenAfter(nodeOrToken, { includeComments = false } = {}) {
|
||||
const index = this.#tokenEnds.get(nodeOrToken.range[1]);
|
||||
if (index === undefined) {
|
||||
return null;
|
||||
}
|
||||
let nextIndex = index + 1;
|
||||
const tokens = this.ast.tokens;
|
||||
if (nextIndex >= tokens.length) {
|
||||
return null;
|
||||
}
|
||||
let tokenOrComment = tokens[nextIndex];
|
||||
if (includeComments) {
|
||||
return tokenOrComment;
|
||||
}
|
||||
// skip comments
|
||||
while (tokenOrComment?.type.endsWith("Comment")) {
|
||||
nextIndex++;
|
||||
if (nextIndex >= tokens.length) {
|
||||
return null;
|
||||
}
|
||||
tokenOrComment = tokens[nextIndex];
|
||||
}
|
||||
return tokenOrComment;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export default rule;
|
||||
export type NoDuplicateKeysMessageIds = "duplicateKey";
|
||||
export type NoDuplicateKeysRuleDefinition = JSONRuleDefinition<{
|
||||
MessageIds: NoDuplicateKeysMessageIds;
|
||||
}>;
|
||||
/**
|
||||
* @import { MemberNode } from "@humanwhocodes/momoa";
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"duplicateKey"} NoDuplicateKeysMessageIds
|
||||
* @typedef {JSONRuleDefinition<{ MessageIds: NoDuplicateKeysMessageIds }>} NoDuplicateKeysRuleDefinition
|
||||
*/
|
||||
/** @type {NoDuplicateKeysRuleDefinition} */
|
||||
declare const rule: NoDuplicateKeysRuleDefinition;
|
||||
import type { JSONRuleDefinition } from "../types.js";
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @fileoverview Rule to prevent duplicate keys in JSON.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Imports
|
||||
//-----------------------------------------------------------------------------
|
||||
import { getKey, getRawKey } from "../util.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { MemberNode } from "@humanwhocodes/momoa";
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"duplicateKey"} NoDuplicateKeysMessageIds
|
||||
* @typedef {JSONRuleDefinition<{ MessageIds: NoDuplicateKeysMessageIds }>} NoDuplicateKeysRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
/** @type {NoDuplicateKeysRuleDefinition} */
|
||||
const rule = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
languages: ["json/json", "json/jsonc", "json/json5"],
|
||||
docs: {
|
||||
recommended: true,
|
||||
description: "Disallow duplicate keys in JSON objects",
|
||||
dialects: ["JSON", "JSONC", "JSON5"],
|
||||
url: "https://github.com/eslint/json/tree/main/docs/rules/no-duplicate-keys.md",
|
||||
},
|
||||
messages: {
|
||||
duplicateKey: 'Duplicate key "{{key}}" found.',
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
/** @type {Array<Map<string, MemberNode>|undefined>} */
|
||||
const objectKeys = [];
|
||||
/** @type {Map<string, MemberNode>|undefined} */
|
||||
let keys;
|
||||
return {
|
||||
Object() {
|
||||
objectKeys.push(keys);
|
||||
keys = new Map();
|
||||
},
|
||||
Member(node) {
|
||||
const key = getKey(node);
|
||||
const rawKey = getRawKey(node, context.sourceCode);
|
||||
if (keys.has(key)) {
|
||||
context.report({
|
||||
loc: node.name.loc,
|
||||
messageId: "duplicateKey",
|
||||
data: {
|
||||
key: rawKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
keys.set(key, node);
|
||||
}
|
||||
},
|
||||
"Object:exit"() {
|
||||
keys = objectKeys.pop();
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export default rule;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
export default rule;
|
||||
export type NoEmptyKeysMessageIds = "emptyKey";
|
||||
export type NoEmptyKeysRuleDefinition = JSONRuleDefinition<{
|
||||
MessageIds: NoEmptyKeysMessageIds;
|
||||
}>;
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"emptyKey"} NoEmptyKeysMessageIds
|
||||
* @typedef {JSONRuleDefinition<{ MessageIds: NoEmptyKeysMessageIds }>} NoEmptyKeysRuleDefinition
|
||||
*/
|
||||
/** @type {NoEmptyKeysRuleDefinition} */
|
||||
declare const rule: NoEmptyKeysRuleDefinition;
|
||||
import type { JSONRuleDefinition } from "../types.js";
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @fileoverview Rule to prevent empty keys in JSON.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Imports
|
||||
//-----------------------------------------------------------------------------
|
||||
import { getKey } from "../util.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"emptyKey"} NoEmptyKeysMessageIds
|
||||
* @typedef {JSONRuleDefinition<{ MessageIds: NoEmptyKeysMessageIds }>} NoEmptyKeysRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
/** @type {NoEmptyKeysRuleDefinition} */
|
||||
const rule = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
languages: ["json/json", "json/jsonc", "json/json5"],
|
||||
docs: {
|
||||
recommended: true,
|
||||
description: "Disallow empty keys in JSON objects",
|
||||
dialects: ["JSON", "JSONC", "JSON5"],
|
||||
url: "https://github.com/eslint/json/tree/main/docs/rules/no-empty-keys.md",
|
||||
},
|
||||
messages: {
|
||||
emptyKey: "Empty key found.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
Member(node) {
|
||||
const key = getKey(node);
|
||||
if (key.trim() === "") {
|
||||
context.report({
|
||||
loc: node.name.loc,
|
||||
messageId: "emptyKey",
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export default rule;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
export default rule;
|
||||
export type NoUnnormalizedKeysMessageIds = "unnormalizedKey";
|
||||
export type NoUnnormalizedKeysOptions = {
|
||||
form: string;
|
||||
};
|
||||
export type NoUnnormalizedKeysRuleDefinition = JSONRuleDefinition<{
|
||||
RuleOptions: [NoUnnormalizedKeysOptions];
|
||||
MessageIds: NoUnnormalizedKeysMessageIds;
|
||||
}>;
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"unnormalizedKey"} NoUnnormalizedKeysMessageIds
|
||||
* @typedef {{ form: string }} NoUnnormalizedKeysOptions
|
||||
* @typedef {JSONRuleDefinition<{ RuleOptions: [NoUnnormalizedKeysOptions], MessageIds: NoUnnormalizedKeysMessageIds }>} NoUnnormalizedKeysRuleDefinition
|
||||
*/
|
||||
/** @type {NoUnnormalizedKeysRuleDefinition} */
|
||||
declare const rule: NoUnnormalizedKeysRuleDefinition;
|
||||
import type { JSONRuleDefinition } from "../types.js";
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @fileoverview Rule to detect unnormalized keys in JSON.
|
||||
* @author Bradley Meck Farias
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Imports
|
||||
//-----------------------------------------------------------------------------
|
||||
import { getKey, getRawKey } from "../util.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"unnormalizedKey"} NoUnnormalizedKeysMessageIds
|
||||
* @typedef {{ form: string }} NoUnnormalizedKeysOptions
|
||||
* @typedef {JSONRuleDefinition<{ RuleOptions: [NoUnnormalizedKeysOptions], MessageIds: NoUnnormalizedKeysMessageIds }>} NoUnnormalizedKeysRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
/** @type {NoUnnormalizedKeysRuleDefinition} */
|
||||
const rule = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
languages: ["json/json", "json/jsonc", "json/json5"],
|
||||
fixable: "code",
|
||||
docs: {
|
||||
recommended: true,
|
||||
description: "Disallow JSON keys that are not normalized",
|
||||
dialects: ["JSON", "JSONC", "JSON5"],
|
||||
url: "https://github.com/eslint/json/tree/main/docs/rules/no-unnormalized-keys.md",
|
||||
},
|
||||
messages: {
|
||||
unnormalizedKey: "Unnormalized key '{{key}}' found.",
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
form: {
|
||||
enum: ["NFC", "NFD", "NFKC", "NFKD"],
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
defaultOptions: [
|
||||
{
|
||||
form: "NFC",
|
||||
},
|
||||
],
|
||||
},
|
||||
create(context) {
|
||||
const [{ form }] = context.options;
|
||||
return {
|
||||
Member(node) {
|
||||
const key = getKey(node);
|
||||
const rawKey = getRawKey(node, context.sourceCode);
|
||||
const normalizedKey = key.normalize(form);
|
||||
if (normalizedKey !== key) {
|
||||
const { name } = node;
|
||||
context.report({
|
||||
loc: name.loc,
|
||||
messageId: "unnormalizedKey",
|
||||
data: {
|
||||
key: rawKey,
|
||||
},
|
||||
fix(fixer) {
|
||||
if (key !== rawKey) {
|
||||
// Do not perform auto-fix when the raw key contains escape sequences.
|
||||
return null;
|
||||
}
|
||||
return fixer.replaceTextRange(name.type === "String"
|
||||
? [name.range[0] + 1, name.range[1] - 1]
|
||||
: name.range, normalizedKey);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export default rule;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
export default rule;
|
||||
export type NoUnsafeValuesMessageIds = "unsafeNumber" | "unsafeInteger" | "unsafeZero" | "subnormal" | "loneSurrogate";
|
||||
export type NoUnsafeValuesRuleDefinition = JSONRuleDefinition<{
|
||||
MessageIds: NoUnsafeValuesMessageIds;
|
||||
}>;
|
||||
/** @type {NoUnsafeValuesRuleDefinition} */
|
||||
declare const rule: NoUnsafeValuesRuleDefinition;
|
||||
import type { JSONRuleDefinition } from "../types.js";
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @fileoverview Rule to detect unsafe values in JSON.
|
||||
* @author Bradley Meck Farias
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"unsafeNumber"|"unsafeInteger"|"unsafeZero"|"subnormal"|"loneSurrogate"} NoUnsafeValuesMessageIds
|
||||
* @typedef {JSONRuleDefinition<{ MessageIds: NoUnsafeValuesMessageIds }>} NoUnsafeValuesRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
/*
|
||||
* This rule is based on the JSON grammar from RFC 8259, section 6.
|
||||
* https://tools.ietf.org/html/rfc8259#section-6
|
||||
*
|
||||
* Also, this rule is based on the JSON5 grammar from json5.org, section 6.
|
||||
* https://spec.json5.org/#numbers
|
||||
*
|
||||
* We separately capture the integer and fractional parts of a number, so that
|
||||
* we can check for unsafe numbers that will evaluate to Infinity.
|
||||
*/
|
||||
const NUMBER = /^[+-]?(?<int>0|([1-9]\d*))?(?:\.(?<frac>\d*))?(?:e[+-]?\d+)?$/iu;
|
||||
const NON_ZERO = /[1-9]/u;
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
/** @type {NoUnsafeValuesRuleDefinition} */
|
||||
const rule = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
languages: ["json/json", "json/jsonc", "json/json5"],
|
||||
docs: {
|
||||
recommended: true,
|
||||
description: "Disallow JSON values that are unsafe for interchange",
|
||||
dialects: ["JSON", "JSONC", "JSON5"],
|
||||
url: "https://github.com/eslint/json/tree/main/docs/rules/no-unsafe-values.md",
|
||||
},
|
||||
messages: {
|
||||
unsafeNumber: "The number '{{ value }}' will evaluate to Infinity.",
|
||||
unsafeInteger: "The integer '{{ value }}' is outside the safe integer range.",
|
||||
unsafeZero: "The number '{{ value }}' will evaluate to zero.",
|
||||
subnormal: "Unexpected subnormal number '{{ value }}' found, which may cause interoperability issues.",
|
||||
loneSurrogate: "Lone surrogate '{{ surrogate }}' found.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
Number(node) {
|
||||
const value = context.sourceCode.getText(node);
|
||||
if (Number.isFinite(node.value) !== true) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "unsafeNumber",
|
||||
data: { value },
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Also matches -0, intentionally
|
||||
if (node.value === 0) {
|
||||
// If the value has been rounded down to 0, but there was some
|
||||
// fraction or non-zero part before the e-, this is a very small
|
||||
// number that doesn't fit inside an f64.
|
||||
const match = value.match(NUMBER);
|
||||
if (match === null) {
|
||||
return;
|
||||
}
|
||||
// If any part of the number other than the exponent has a
|
||||
// non-zero digit in it, this number was not intended to be
|
||||
// evaluated down to a zero.
|
||||
if (NON_ZERO.test(match.groups.int) ||
|
||||
NON_ZERO.test(match.groups.frac)) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "unsafeZero",
|
||||
data: { value },
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (!/[.e]/iu.test(value)) {
|
||||
// Intended to be an integer
|
||||
if (node.value > Number.MAX_SAFE_INTEGER ||
|
||||
node.value < Number.MIN_SAFE_INTEGER) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "unsafeInteger",
|
||||
data: { value },
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Floating point. Check for subnormal.
|
||||
const buffer = new ArrayBuffer(8);
|
||||
const view = new DataView(buffer);
|
||||
view.setFloat64(0, node.value, false);
|
||||
const asBigInt = view.getBigUint64(0, false);
|
||||
// Subnormals have an 11-bit exponent of 0 and a non-zero mantissa.
|
||||
if ((asBigInt & 0x7ff0000000000000n) === 0n) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "subnormal",
|
||||
// Value included so that it's seen in scientific notation
|
||||
data: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
String(node) {
|
||||
if (node.value.isWellFormed) {
|
||||
if (node.value.isWellFormed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// match any high surrogate and, if it exists, a paired low surrogate
|
||||
// match any low surrogate not already matched
|
||||
const surrogatePattern = /[\uD800-\uDBFF][\uDC00-\uDFFF]?|[\uDC00-\uDFFF]/gu;
|
||||
/** @type {RegExpExecArray | null} */
|
||||
let match;
|
||||
while ((match = surrogatePattern.exec(node.value)) !== null) {
|
||||
// only need to report non-paired surrogates
|
||||
if (match[0].length < 2) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "loneSurrogate",
|
||||
data: {
|
||||
surrogate: JSON.stringify(match[0]).slice(1, -1),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export default rule;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
export default rule;
|
||||
export type SortOptions = {
|
||||
/**
|
||||
* Whether key comparisons are case-sensitive.
|
||||
*/
|
||||
caseSensitive: boolean;
|
||||
/**
|
||||
* Whether to use natural sort order instead of purely alphanumeric.
|
||||
*/
|
||||
natural: boolean;
|
||||
/**
|
||||
* Minimum number of keys in an object before enforcing sorting.
|
||||
*/
|
||||
minKeys: number;
|
||||
/**
|
||||
* Whether a blank line between properties starts a new group that is independently sorted.
|
||||
*/
|
||||
allowLineSeparatedGroups: boolean;
|
||||
};
|
||||
export type SortKeysMessageIds = "sortKeys";
|
||||
export type SortDirection = "asc" | "desc";
|
||||
export type SortKeysRuleOptions = [SortDirection, SortOptions];
|
||||
export type SortKeysRuleDefinition = JSONRuleDefinition<{
|
||||
RuleOptions: SortKeysRuleOptions;
|
||||
MessageIds: SortKeysMessageIds;
|
||||
}>;
|
||||
export type Comparator = (a: string, b: string) => boolean;
|
||||
export type DirectionName = "ascending" | "descending";
|
||||
export type SortName = "alphanumeric" | "natural";
|
||||
export type Sensitivity = "sensitive" | "insensitive";
|
||||
export type ComparatorMap = Record<DirectionName, Record<SortName, Record<Sensitivity, Comparator>>>;
|
||||
/** @type {SortKeysRuleDefinition} */
|
||||
declare const rule: SortKeysRuleDefinition;
|
||||
import type { JSONRuleDefinition } from "../types.js";
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* @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";
|
||||
import { getKey, getRawKey } from "../util.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @import { MemberNode } from "@humanwhocodes/momoa";
|
||||
* @typedef {Object} SortOptions
|
||||
* @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
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
const hasNonWhitespace = /\S/u;
|
||||
const commentTypes = new Set(["LineComment", "BlockComment"]);
|
||||
/** @type {ComparatorMap} */
|
||||
const comparators = {
|
||||
ascending: {
|
||||
alphanumeric: {
|
||||
sensitive: (a, b) => a <= b,
|
||||
insensitive: (a, b) => a.toLowerCase() <= b.toLowerCase(),
|
||||
},
|
||||
natural: {
|
||||
sensitive: (a, b) => naturalCompare(a, b) <= 0,
|
||||
insensitive: (a, b) => naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0,
|
||||
},
|
||||
},
|
||||
descending: {
|
||||
alphanumeric: {
|
||||
sensitive: (a, b) => comparators.ascending.alphanumeric.sensitive(b, a),
|
||||
insensitive: (a, b) => comparators.ascending.alphanumeric.insensitive(b, a),
|
||||
},
|
||||
natural: {
|
||||
sensitive: (a, b) => comparators.ascending.natural.sensitive(b, a),
|
||||
insensitive: (a, b) => comparators.ascending.natural.insensitive(b, a),
|
||||
},
|
||||
},
|
||||
};
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
/** @type {SortKeysRuleDefinition} */
|
||||
const rule = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
languages: ["json/json", "json/jsonc", "json/json5"],
|
||||
fixable: "code",
|
||||
defaultOptions: [
|
||||
"asc",
|
||||
{
|
||||
allowLineSeparatedGroups: false,
|
||||
caseSensitive: true,
|
||||
minKeys: 2,
|
||||
natural: false,
|
||||
},
|
||||
],
|
||||
docs: {
|
||||
recommended: false,
|
||||
description: `Require JSON object keys to be sorted`,
|
||||
dialects: ["JSON", "JSONC", "JSON5"],
|
||||
url: "https://github.com/eslint/json/tree/main/docs/rules/sort-keys.md",
|
||||
},
|
||||
messages: {
|
||||
sortKeys: "Expected object keys to be in {{sortName}} case-{{sensitivity}} {{direction}} order. '{{thisName}}' should be before '{{prevName}}'.",
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
enum: ["asc", "desc"],
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
caseSensitive: {
|
||||
type: "boolean",
|
||||
},
|
||||
natural: {
|
||||
type: "boolean",
|
||||
},
|
||||
minKeys: {
|
||||
type: "integer",
|
||||
minimum: 2,
|
||||
},
|
||||
allowLineSeparatedGroups: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
create(context) {
|
||||
const { sourceCode } = context;
|
||||
const [directionShort, { 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
|
||||
const commentLineNums = new Set();
|
||||
for (const comment of sourceCode.comments) {
|
||||
for (let lineNum = comment.loc.start.line; lineNum <= comment.loc.end.line; lineNum += 1) {
|
||||
commentLineNums.add(lineNum);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks if two members are line-separated.
|
||||
* @param {MemberNode} prevMember The previous member.
|
||||
* @param {MemberNode} member The current member.
|
||||
* @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
|
||||
const prevMemberEndLine = prevMember.loc.end.line;
|
||||
const thisStartLine = member.loc.start.line;
|
||||
if (thisStartLine - prevMemberEndLine < 2) {
|
||||
return false;
|
||||
}
|
||||
for (let lineNum = prevMemberEndLine + 1; lineNum < thisStartLine; lineNum += 1) {
|
||||
if (!commentLineNums.has(lineNum) &&
|
||||
!hasNonWhitespace.test(sourceCode.lines[lineNum - 1])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Checks if a member has a comment before or after it.
|
||||
* @param {MemberNode} member The member to check.
|
||||
* @returns {boolean} True if a comment is adjacent to the member.
|
||||
*/
|
||||
function hasAdjacentComment(member) {
|
||||
const before = sourceCode.getTokenBefore(member, {
|
||||
includeComments: true,
|
||||
});
|
||||
let after = sourceCode.getTokenAfter(member, {
|
||||
includeComments: true,
|
||||
});
|
||||
if (after.type === "Comma") {
|
||||
after = sourceCode.getTokenAfter(after, {
|
||||
includeComments: true,
|
||||
});
|
||||
}
|
||||
return (commentTypes.has(before.type) || commentTypes.has(after.type));
|
||||
}
|
||||
return {
|
||||
Object(node) {
|
||||
/** @type {MemberNode} */
|
||||
let prevMember;
|
||||
/** @type {string} */
|
||||
let prevName;
|
||||
/** @type {string} */
|
||||
let prevRawName;
|
||||
if (node.members.length < minKeys) {
|
||||
return;
|
||||
}
|
||||
for (const member of node.members) {
|
||||
const thisName = getKey(member);
|
||||
const thisRawName = getRawKey(member, sourceCode);
|
||||
// Capture `prevMember` for this iteration so the fixer closure uses the
|
||||
// intended node even though `prevMember` is reassigned in the loop.
|
||||
const prevMemberNode = prevMember;
|
||||
if (prevMember &&
|
||||
!isValidOrder(prevName, thisName) &&
|
||||
(!allowLineSeparatedGroups ||
|
||||
!isLineSeparated(prevMember, member))) {
|
||||
context.report({
|
||||
loc: member.name.loc,
|
||||
messageId: "sortKeys",
|
||||
data: {
|
||||
thisName: thisRawName,
|
||||
prevName: prevRawName,
|
||||
direction,
|
||||
sensitivity,
|
||||
sortName,
|
||||
},
|
||||
fix(fixer) {
|
||||
if (hasAdjacentComment(member) ||
|
||||
hasAdjacentComment(prevMemberNode)) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
fixer.replaceText(member, sourceCode.getText(prevMemberNode)),
|
||||
fixer.replaceText(prevMemberNode, sourceCode.getText(member)),
|
||||
];
|
||||
},
|
||||
});
|
||||
}
|
||||
prevMember = member;
|
||||
prevName = thisName;
|
||||
prevRawName = thisRawName;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export default rule;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
export default rule;
|
||||
export type TopLevelInteropMessageIds = "topLevel";
|
||||
export type TopLevelInteropRuleDefinition = JSONRuleDefinition<{
|
||||
MessageIds: TopLevelInteropMessageIds;
|
||||
}>;
|
||||
/**
|
||||
* @fileoverview Rule to ensure top-level items are either an array or object.
|
||||
* @author Joe Hildebrand
|
||||
*/
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"topLevel"} TopLevelInteropMessageIds
|
||||
* @typedef {JSONRuleDefinition<{ MessageIds: TopLevelInteropMessageIds }>} TopLevelInteropRuleDefinition
|
||||
*/
|
||||
/** @type {TopLevelInteropRuleDefinition} */
|
||||
declare const rule: TopLevelInteropRuleDefinition;
|
||||
import type { JSONRuleDefinition } from "../types.js";
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @fileoverview Rule to ensure top-level items are either an array or object.
|
||||
* @author Joe Hildebrand
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { JSONRuleDefinition } from "../types.js";
|
||||
* @typedef {"topLevel"} TopLevelInteropMessageIds
|
||||
* @typedef {JSONRuleDefinition<{ MessageIds: TopLevelInteropMessageIds }>} TopLevelInteropRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
/** @type {TopLevelInteropRuleDefinition} */
|
||||
const rule = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
languages: ["json/json", "json/jsonc", "json/json5"],
|
||||
docs: {
|
||||
recommended: false,
|
||||
description: "Require the JSON top-level value to be an array or object",
|
||||
dialects: ["JSON", "JSONC", "JSON5"],
|
||||
url: "https://github.com/eslint/json/tree/main/docs/rules/top-level-interop.md",
|
||||
},
|
||||
messages: {
|
||||
topLevel: "Top level item should be array or object, got '{{type}}'.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
Document(node) {
|
||||
const { type } = node.body;
|
||||
if (type !== "Object" && type !== "Array") {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "topLevel",
|
||||
data: { type },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
export default rule;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @fileoverview Additional types for this package.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
import type { RuleVisitor } from "@eslint/core";
|
||||
import type { CustomRuleDefinitionType, CustomRuleTypeDefinitions, CustomRuleVisitorWithExit } from "@eslint/plugin-kit";
|
||||
import type { DocumentNode, MemberNode, ElementNode, ObjectNode, ArrayNode, StringNode, NullNode, NumberNode, BooleanNode, NaNNode, InfinityNode, IdentifierNode, AnyNode, Token } from "@humanwhocodes/momoa";
|
||||
import type { JSONLanguageOptions, JSONSourceCode } from "./index.js";
|
||||
type ValueNodeParent = DocumentNode | MemberNode | ElementNode;
|
||||
/**
|
||||
* A JSON syntax element, including nodes and tokens.
|
||||
*/
|
||||
export type JSONSyntaxElement = Token | AnyNode;
|
||||
/**
|
||||
* The visitor format returned from rules in this package.
|
||||
*/
|
||||
export interface JSONRuleVisitor extends RuleVisitor, CustomRuleVisitorWithExit<{
|
||||
Document?(node: DocumentNode): void;
|
||||
Member?(node: MemberNode, parent?: ObjectNode): void;
|
||||
Element?(node: ElementNode, parent?: ArrayNode): void;
|
||||
Object?(node: ObjectNode, parent?: ValueNodeParent): void;
|
||||
Array?(node: ArrayNode, parent?: ValueNodeParent): void;
|
||||
String?(node: StringNode, parent?: ValueNodeParent): void;
|
||||
Null?(node: NullNode, parent?: ValueNodeParent): void;
|
||||
Number?(node: NumberNode, parent?: ValueNodeParent): void;
|
||||
Boolean?(node: BooleanNode, parent?: ValueNodeParent): void;
|
||||
NaN?(node: NaNNode, parent?: ValueNodeParent): void;
|
||||
Infinity?(node: InfinityNode, parent?: ValueNodeParent): void;
|
||||
Identifier?(node: IdentifierNode, parent?: ValueNodeParent): void;
|
||||
}> {
|
||||
}
|
||||
export type JSONRuleDefinitionTypeOptions = CustomRuleTypeDefinitions;
|
||||
export type JSONRuleDefinition<Options extends Partial<JSONRuleDefinitionTypeOptions> = {}> = CustomRuleDefinitionType<{
|
||||
LangOptions: JSONLanguageOptions;
|
||||
Code: JSONSourceCode;
|
||||
Visitor: JSONRuleVisitor;
|
||||
Node: JSONSyntaxElement;
|
||||
}, Options>;
|
||||
export {};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @fileoverview Additional types for this package.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
export {};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @fileoverview Utility Library
|
||||
* @author 루밀LuMir(lumirlumir)
|
||||
*/
|
||||
/**
|
||||
* @import { MemberNode } from "@humanwhocodes/momoa";
|
||||
* @import { JSONSourceCode } from "./languages/json-source-code.js";
|
||||
*/
|
||||
/**
|
||||
* Gets the `MemberNode`'s key value.
|
||||
* @param {MemberNode} node The node to get the key from.
|
||||
* @returns {string} The key value.
|
||||
*/
|
||||
export function getKey(node: MemberNode): string;
|
||||
/**
|
||||
* Gets the `MemberNode`'s raw key value.
|
||||
* @param {MemberNode} node The node to get the raw key from.
|
||||
* @param {JSONSourceCode} sourceCode The JSON source code object.
|
||||
* @returns {string} The raw key value.
|
||||
*/
|
||||
export function getRawKey(node: MemberNode, sourceCode: JSONSourceCode): string;
|
||||
import type { MemberNode } from "@humanwhocodes/momoa";
|
||||
import type { JSONSourceCode } from "./languages/json-source-code.js";
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* @fileoverview Utility Library
|
||||
* @author 루밀LuMir(lumirlumir)
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { MemberNode } from "@humanwhocodes/momoa";
|
||||
* @import { JSONSourceCode } from "./languages/json-source-code.js";
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Gets the `MemberNode`'s key value.
|
||||
* @param {MemberNode} node The node to get the key from.
|
||||
* @returns {string} The key value.
|
||||
*/
|
||||
export function getKey(node) {
|
||||
return node.name.type === "String" ? node.name.value : node.name.name;
|
||||
}
|
||||
/**
|
||||
* Gets the `MemberNode`'s raw key value.
|
||||
* @param {MemberNode} node The node to get the raw key from.
|
||||
* @param {JSONSourceCode} sourceCode The JSON source code object.
|
||||
* @returns {string} The raw key value.
|
||||
*/
|
||||
export function getRawKey(node, sourceCode) {
|
||||
return node.name.type === "String"
|
||||
? sourceCode.getText(node.name, -1, -1)
|
||||
: sourceCode.getText(node.name);
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "@eslint/json",
|
||||
"version": "2.0.1",
|
||||
"description": "JSON linting plugin for ESLint",
|
||||
"author": "Nicholas C. Zakas",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"!(*.js)": "prettier --write --ignore-unknown",
|
||||
"{src/rules/*.js,tools/update-rules-docs.js,README.md}": [
|
||||
"npm run build:update-rules-docs",
|
||||
"git add README.md"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/eslint/json.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/eslint/json/issues"
|
||||
},
|
||||
"homepage": "https://github.com/eslint/json#readme",
|
||||
"scripts": {
|
||||
"build": "npm run build:rules && npm run build:types && npm run build:update-rules-docs",
|
||||
"build:rules": "node tools/build-rules.js",
|
||||
"build:types": "tsc",
|
||||
"build:update-rules-docs": "node tools/update-rules-docs.js",
|
||||
"prepare": "npm run build",
|
||||
"pretest": "npm run build",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"lint:types": "attw --pack --profile esm-only",
|
||||
"lint:unused": "knip",
|
||||
"fmt": "prettier --write .",
|
||||
"fmt:check": "prettier --check .",
|
||||
"test": "mocha \"tests/**/*.test.js\"",
|
||||
"test:coverage": "c8 npm test",
|
||||
"test:jsr": "npx -y jsr@latest publish --dry-run",
|
||||
"test:types": "tsc -p tests/types/tsconfig.json",
|
||||
"test:types:5.3": "npx -p typescript@5.3 -y -- tsc -p tests/types/tsconfig.legacy.json",
|
||||
"test:types:5.x": "npx -p typescript@5.x -y -- tsc -p tests/types/tsconfig.json",
|
||||
"test:types:7.x": "npx -p @typescript/native-preview@latest -y -- tsgo -p tests/types/tsconfig.json",
|
||||
"test:types:all": "npm run test:types && npm run test:types:5.3 && npm run test:types:5.x && npm run test:types:7.x"
|
||||
},
|
||||
"keywords": [
|
||||
"eslint",
|
||||
"eslint-plugin",
|
||||
"eslintplugin",
|
||||
"json",
|
||||
"linting"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^1.2.1",
|
||||
"@eslint/plugin-kit": "^0.7.2",
|
||||
"@humanwhocodes/momoa": "^3.3.10",
|
||||
"natural-compare": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.18.3",
|
||||
"@types/natural-compare": "^1.4.3",
|
||||
"@types/node": "^20.19.0",
|
||||
"c8": "^11.0.0",
|
||||
"dedent": "^1.5.3",
|
||||
"eslint": "^10.4.0",
|
||||
"eslint-config-eslint": "^14.0.0",
|
||||
"eslint-plugin-eslint-plugin": "^7.3.2",
|
||||
"globals": "^17.0.0",
|
||||
"knip": "^6.0.0",
|
||||
"lint-staged": "^16.0.0",
|
||||
"mdast-util-from-markdown": "^2.0.2",
|
||||
"mocha": "^11.3.0",
|
||||
"prettier": "3.8.4",
|
||||
"typescript": "^6.0.2",
|
||||
"yorkie": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user