routie dev init since i didn't adhere to any proper guidance up until now

This commit is contained in:
2026-04-29 22:27:29 -06:00
commit e1dabb71e2
15301 changed files with 3562618 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Yosuke Ota
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+348
View File
@@ -0,0 +1,348 @@
# Introduction
[eslint-plugin-jsonc](https://www.npmjs.com/package/eslint-plugin-jsonc) is ESLint plugin for [JSON], [JSONC] and [JSON5] files.
[![NPM license](https://img.shields.io/npm/l/eslint-plugin-jsonc.svg)](https://www.npmjs.com/package/eslint-plugin-jsonc)
[![NPM version](https://img.shields.io/npm/v/eslint-plugin-jsonc.svg)](https://www.npmjs.com/package/eslint-plugin-jsonc)
[![NPM downloads](https://img.shields.io/badge/dynamic/json.svg?label=downloads&colorB=green&suffix=/day&query=$.downloads&uri=https://api.npmjs.org//downloads/point/last-day/eslint-plugin-jsonc&maxAge=3600)](http://www.npmtrends.com/eslint-plugin-jsonc)
[![NPM downloads](https://img.shields.io/npm/dw/eslint-plugin-jsonc.svg)](http://www.npmtrends.com/eslint-plugin-jsonc)
[![NPM downloads](https://img.shields.io/npm/dm/eslint-plugin-jsonc.svg)](http://www.npmtrends.com/eslint-plugin-jsonc)
[![NPM downloads](https://img.shields.io/npm/dy/eslint-plugin-jsonc.svg)](http://www.npmtrends.com/eslint-plugin-jsonc)
[![NPM downloads](https://img.shields.io/npm/dt/eslint-plugin-jsonc.svg)](http://www.npmtrends.com/eslint-plugin-jsonc)
[![Build Status](https://github.com/ota-meshi/eslint-plugin-jsonc/workflows/CI/badge.svg?branch=master)](https://github.com/ota-meshi/eslint-plugin-jsonc/actions?query=workflow%3ACI)
[![Coverage Status](https://coveralls.io/repos/github/ota-meshi/eslint-plugin-jsonc/badge.svg?branch=master)](https://coveralls.io/github/ota-meshi/eslint-plugin-jsonc?branch=master)
## :name_badge: Features
This ESLint plugin provides linting rules relate to better ways to help you avoid problems when using [JSON], [JSONC] and [JSON5].
- You can use ESLint to lint [JSON].
- You can apply rules similar to the rules you use for JavaScript to JSON using the [`"jsonc/auto"`](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/auto.html) rule provided by this plugin.
- You can choose the appropriate config provided by this plugin depending on whether you are using [JSON], [JSONC] or [JSON5].
- Supports [Vue SFC](https://vue-loader.vuejs.org/spec.html) custom blocks such as `<i18n>`.
Requirements `vue-eslint-parser` v7.3.0 and above.
- Supports ESLint directives. e.g. `// eslint-disable-next-line`
- You can check your code in real-time using the ESLint editor integrations.
You can check on the [Online DEMO](https://ota-meshi.github.io/eslint-plugin-jsonc/playground/).
## :question: Why is it ESLint plugin?
ESLint is a great linter for JavaScript.
Since [JSON] is a subset of JavaScript, the same parser and rules can be applied to [JSON].
Also, [JSONC] and [JSON5], which are variants of [JSON], are more similar to JavaScript than [JSON]. Applying a JavaScript linter to [JSON] is more rational than creating a JSON-specific linter.
### How does `eslint-plugin-jsonc` work?
This plugin parses `.json` with its own parser, but this parser just converts AST parsed by `acorn` (It is used internally by the ESLint standard parser) into AST with another name. However, ASTs that do not exist in [JSON] and the superset of JSON syntaxes are reported as parsing errors. By converting the AST to another name, we prevent false positives from ESLint core rules.
Moreover, You can do the same linting using the extended rules of the ESLint core rules provided by this plugin.
The parser package used by this plugin is [jsonc-eslint-parser].
## :question: How is it different from other JSON plugins?
### [`@eslint/json`]
They work similarly, but [`@eslint/json`] is an ESLint JSON language plugin, but `eslint-plugin-jsonc` is an ESLint plugin.
- `eslint-plugin-jsonc` was created 4 years before [`@eslint/json`] and it has more rules than [`@eslint/json`].
- The parser used by `eslint-plugin-jsonc` accepts more non-standard JSON syntax than [`@eslint/json`]. `eslint-plugin-jsonc` has rules that can auto-fixed these non-standard syntax to standard syntax.
- `eslint-plugin-jsonc` can also be used together with [`@eslint/json`].
### Plugins that do not use AST
e.g. [eslint-plugin-json](https://www.npmjs.com/package/eslint-plugin-json)
These plugins use the processor to parse and return the results independently, without providing the ESLint engine with AST and source code text.
Plugins don't provide AST, so you can't use directive comments (e.g. `/* eslint-disable */`).
Plugins don't provide source code text, so you can't use it with plugins and rules that use text (e.g. [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier), [eol-last](https://eslint.org/docs/rules/eol-last)).
Also, most plugins don't support JSON5.
**eslint-plugin-jsonc** works by providing AST and source code text to ESLint.
### Plugins that use the same AST as JavaScript
e.g. [eslint-plugin-json-files](https://www.npmjs.com/package/eslint-plugin-json-files), [eslint-plugin-json-es](https://www.npmjs.com/package/eslint-plugin-json-es)
These plugins use the same AST as JavaScript for linting.
Since the plugin uses the same AST as JavaScript, it may not report syntax that is not available in JSON (e.g. `1 + 1`, `(42)`). Also, ESLint core rules and other plugin rules can false positives (e.g. [quote-props](https://eslint.org/docs/rules/quote-props) rule reports quote on keys), which can complicate the your configuration.
The AST used by **eslint-plugin-jsonc** is similar to JavaScript AST, but with a different node name. This will prevent false positives. This means that it can be easily used in combination with other plugins.
<!--DOCS_IGNORE_START-->
## :book: Documentation
See [documents](https://ota-meshi.github.io/eslint-plugin-jsonc/).
## :cd: Installation
```bash
npm install --save-dev eslint eslint-plugin-jsonc
```
> **Requirements**
>
> - ESLint v9.38.0 and above
> - Node.js v20.x (>=20.19.0), v22.x (>=22.13.0), v24.x and above
<!--DOCS_IGNORE_END-->
## :book: Usage
<!--USAGE_SECTION_START-->
<!--USAGE_GUIDE_START-->
### Configuration
#### Configuration (`eslint.config.js`)
Use `eslint.config.js` file to configure rules. See also: <https://eslint.org/docs/latest/use/configure/configuration-files-new>.
Example **eslint.config.js**:
```js
import eslintPluginJsonc from 'eslint-plugin-jsonc';
export default [
// add more generic rule sets here, such as:
// js.configs.recommended,
...eslintPluginJsonc.configs['recommended-with-jsonc'],
{
rules: {
// override/add rules settings here, such as:
// 'jsonc/rule-name': 'error'
}
}
];
```
This plugin provides configs:
- `*.configs.base` ... Configuration to enable correct JSON parsing.
- `*.configs['recommended-with-json']` ... Recommended configuration for JSON.
- `*.configs['recommended-with-jsonc']` ... Recommended configuration for JSONC.
- `*.configs['recommended-with-json5']` ... Recommended configuration for JSON5.
- `*.configs.prettier` ... Turn off rules that may conflict with [Prettier](https://prettier.io/).
- `*.configs.all` ... Enables all rules. It's meant for testing, not for production use because it changes with every minor and major version of the plugin. Use it at your own risk.
For backward compatibility, the `flat/*` prefix is still supported:
- `*.configs['flat/base']`, `*.configs['flat/recommended-with-json']`, etc.
This plugin will parse `.json`, `.jsonc` and `.json5` by default using the configuration provided by the plugin (unless you already have a parser configured - see below).
See [the rule list](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/) to get the `rules` that this plugin provides.
#### Languages
This plugin provides the following language identifiers for use in ESLint configurations:
- `jsonc/json` ... JSON files
- `jsonc/jsonc` ... JSONC files
- `jsonc/json5` ... JSON5 files
- `jsonc/x` ... Extended JSON files that accept any syntax representing static values parseable by [jsonc-eslint-parser](https://github.com/ota-meshi/jsonc-eslint-parser). Recommended because it allows flexible parsing while strict syntax checks can be enforced and auto-fixed using the plugin's rules.
For example, to apply settings specifically to JSON files, you can use the `language` field in your ESLint configuration:
```js
import eslintPluginJsonc from 'eslint-plugin-jsonc';
export default [
{
files: ["*.json", "**/*.json"],
plugins: {
jsonc: eslintPluginJsonc,
},
language: "jsonc/x",
}
]
```
The configuration above is included in the shareable configs provided by this plugin, so using `configs` is generally recommended.
See also <https://eslint.org/docs/latest/use/configure/plugins#specify-a-language>
#### **Experimental** support for `@eslint/json`
We've launched experimental support for [`@eslint/json`].
Configure it as follows:
```js
import json from "@eslint/json";
import jsonc from 'eslint-plugin-jsonc';
export default [
{
plugins: {
json,
jsonc
},
},
{
files: ["**/*.json"],
language: "json/json",
rules: {
// 'json/rule-name': 'error',
// 'jsonc/rule-name': 'error'
},
},
{
files: ["**/*.jsonc", ".vscode/*.json"],
language: "json/jsonc",
rules: {
// 'json/rule-name': 'error',
// 'jsonc/rule-name': 'error'
},
},
{
files: ["**/*.json5"],
language: "json/json5",
rules: {
// 'json/rule-name': 'error',
// 'jsonc/rule-name': 'error'
},
},
];
```
However, we're not yet sure how best to make this work.
Please note that we may change behavior without notice.
[`@eslint/json`]: https://github.com/eslint/json
## :computer: Editor Integrations
### Visual Studio Code
Use the [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) extension that Microsoft provides officially.
You have to configure the `eslint.validate` option of the extension to check `.json` files, because the extension targets only `*.js` or `*.jsx` files by default.
Example **.vscode/settings.json**:
```json
{
"eslint.validate": ["javascript", "javascriptreact", "json", "jsonc", "json5"]
}
```
<!--USAGE_GUIDE_END-->
<!--USAGE_SECTION_END-->
## :white_check_mark: Rules
<!--RULES_SECTION_START-->
The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) automatically fixes problems reported by rules which have a wrench :wrench: below.
The rules with the following star :star: are included in the config.
<!--RULES_TABLE_START-->
### JSONC Rules
| Rule ID | Description | Fixable | JSON | JSONC | JSON5 |
|:--------|:------------|:-------:|:----:|:-----:|:-----:|
| [jsonc/auto](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/auto.html) | apply jsonc rules similar to your configured ESLint core rules | :wrench: | | | |
| [jsonc/key-name-casing](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/key-name-casing.html) | enforce naming convention to property key names | | | | |
| [jsonc/no-bigint-literals](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-bigint-literals.html) | disallow BigInt literals | | :star: | :star: | :star: |
| [jsonc/no-binary-expression](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-binary-expression.html) | disallow binary expression | :wrench: | :star: | :star: | :star: |
| [jsonc/no-binary-numeric-literals](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-binary-numeric-literals.html) | disallow binary numeric literals | :wrench: | :star: | :star: | :star: |
| [jsonc/no-comments](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-comments.html) | disallow comments | | :star: | | |
| [jsonc/no-escape-sequence-in-identifier](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-escape-sequence-in-identifier.html) | disallow escape sequences in identifiers. | :wrench: | :star: | :star: | :star: |
| [jsonc/no-hexadecimal-numeric-literals](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-hexadecimal-numeric-literals.html) | disallow hexadecimal numeric literals | :wrench: | :star: | :star: | |
| [jsonc/no-infinity](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-infinity.html) | disallow Infinity | | :star: | :star: | |
| [jsonc/no-nan](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-nan.html) | disallow NaN | | :star: | :star: | |
| [jsonc/no-number-props](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-number-props.html) | disallow number property keys | :wrench: | :star: | :star: | :star: |
| [jsonc/no-numeric-separators](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-numeric-separators.html) | disallow numeric separators | :wrench: | :star: | :star: | :star: |
| [jsonc/no-octal-numeric-literals](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-octal-numeric-literals.html) | disallow octal numeric literals | :wrench: | :star: | :star: | :star: |
| [jsonc/no-parenthesized](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-parenthesized.html) | disallow parentheses around the expression | :wrench: | :star: | :star: | :star: |
| [jsonc/no-plus-sign](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-plus-sign.html) | disallow plus sign | :wrench: | :star: | :star: | |
| [jsonc/no-regexp-literals](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-regexp-literals.html) | disallow RegExp literals | | :star: | :star: | :star: |
| [jsonc/no-template-literals](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-template-literals.html) | disallow template literals | :wrench: | :star: | :star: | :star: |
| [jsonc/no-undefined-value](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-undefined-value.html) | disallow `undefined` | | :star: | :star: | :star: |
| [jsonc/no-unicode-codepoint-escapes](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-unicode-codepoint-escapes.html) | disallow Unicode code point escape sequences. | :wrench: | :star: | :star: | :star: |
| [jsonc/sort-array-values](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/sort-array-values.html) | require array values to be sorted | :wrench: | | | |
| [jsonc/sort-keys](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/sort-keys.html) | require object keys to be sorted | :wrench: | | | |
| [jsonc/valid-json-number](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/valid-json-number.html) | disallow invalid number for JSON | :wrench: | :star: | :star: | |
| [jsonc/vue-custom-block/no-parsing-error](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/vue-custom-block/no-parsing-error.html) | disallow parsing errors in Vue custom blocks | | :star: | :star: | :star: |
### Extension Rules
| Rule ID | Description | Fixable | JSON | JSONC | JSON5 |
|:--------|:------------|:-------:|:----:|:-----:|:-----:|
| [jsonc/array-bracket-newline](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/array-bracket-newline.html) | enforce line breaks after opening and before closing array brackets | :wrench: | | | |
| [jsonc/array-bracket-spacing](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/array-bracket-spacing.html) | disallow or enforce spaces inside of brackets | :wrench: | | | |
| [jsonc/array-element-newline](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/array-element-newline.html) | enforce line breaks between array elements | :wrench: | | | |
| [jsonc/comma-dangle](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/comma-dangle.html) | require or disallow trailing commas | :wrench: | :star: | | |
| [jsonc/comma-style](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/comma-style.html) | enforce consistent comma style | :wrench: | | | |
| [jsonc/indent](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/indent.html) | enforce consistent indentation | :wrench: | | | |
| [jsonc/key-spacing](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/key-spacing.html) | enforce consistent spacing between keys and values in object literal properties | :wrench: | | | |
| [jsonc/no-dupe-keys](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-dupe-keys.html) | disallow duplicate keys in object literals | | :star: | :star: | :star: |
| [jsonc/no-floating-decimal](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-floating-decimal.html) | disallow leading or trailing decimal points in numeric literals | :wrench: | :star: | :star: | |
| [jsonc/no-irregular-whitespace](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-irregular-whitespace.html) | disallow irregular whitespace | | :star: | :star: | :star: |
| [jsonc/no-multi-str](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-multi-str.html) | disallow multiline strings | | :star: | :star: | |
| [jsonc/no-octal-escape](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-octal-escape.html) | disallow octal escape sequences in string literals | | | | |
| [jsonc/no-octal](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-octal.html) | disallow legacy octal literals | | :star: | :star: | :star: |
| [jsonc/no-sparse-arrays](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-sparse-arrays.html) | disallow sparse arrays | | :star: | :star: | :star: |
| [jsonc/no-useless-escape](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-useless-escape.html) | disallow unnecessary escape usage | | :star: | :star: | :star: |
| [jsonc/object-curly-newline](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/object-curly-newline.html) | enforce consistent line breaks inside braces | :wrench: | | | |
| [jsonc/object-curly-spacing](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/object-curly-spacing.html) | enforce consistent spacing inside braces | :wrench: | | | |
| [jsonc/object-property-newline](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/object-property-newline.html) | enforce placing object properties on separate lines | :wrench: | | | |
| [jsonc/quote-props](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/quote-props.html) | require quotes around object literal property names | :wrench: | :star: | :star: | |
| [jsonc/quotes](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/quotes.html) | enforce use of double or single quotes | :wrench: | :star: | :star: | |
| [jsonc/space-unary-ops](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/space-unary-ops.html) | disallow spaces after unary operators | :wrench: | :star: | :star: | :star: |
<!--RULES_TABLE_END-->
<!--RULES_SECTION_END-->
## :rocket: To Do More Verification
### Verify using JSON Schema
You can verify using JSON Schema by checking and installing [eslint-plugin-json-schema-validator].
### Verify the [Vue I18n] message resource files
You can verify the message files by checking and installing [@intlify/eslint-plugin-vue-i18n].
<!--DOCS_IGNORE_START-->
## :traffic_light: Semantic Versioning Policy
**eslint-plugin-jsonc** follows [Semantic Versioning](http://semver.org/) and [ESLint's Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy).
## :beers: Contributing
Welcome contributing!
Please use GitHub's Issues/PRs.
### Development Tools
- `npm test` runs tests and measures coverage.
- `npm run update` runs in order to update readme and recommended configuration.
<!--DOCS_IGNORE_END-->
## :couple: Related Packages
- [eslint-plugin-yml](https://github.com/ota-meshi/eslint-plugin-yml) ... ESLint plugin for YAML.
- [eslint-plugin-toml](https://github.com/ota-meshi/eslint-plugin-toml) ... ESLint plugin for TOML.
- [eslint-plugin-json-schema-validator](https://github.com/ota-meshi/eslint-plugin-json-schema-validator) ... ESLint plugin that validates data using JSON Schema Validator.
- [jsonc-eslint-parser](https://github.com/ota-meshi/jsonc-eslint-parser) ... JSON, JSONC and JSON5 parser for use with ESLint plugins.
- [yaml-eslint-parser](https://github.com/ota-meshi/yaml-eslint-parser) ... YAML parser for use with ESLint plugins.
- [toml-eslint-parser](https://github.com/ota-meshi/toml-eslint-parser) ... TOML parser for use with ESLint plugins.
## :lock: License
See the [LICENSE](LICENSE) file for license rights and limitations (MIT).
[json]: https://json.org/
[jsonc]: https://github.com/microsoft/node-jsonc-parser
[json5]: https://json5.org/
[jsonc-eslint-parser]: https://github.com/ota-meshi/jsonc-eslint-parser
[eslint-plugin-json-schema-validator]: https://github.com/ota-meshi/eslint-plugin-json-schema-validator
[@intlify/eslint-plugin-vue-i18n]: https://github.com/intlify/eslint-plugin-vue-i18n
[vue i18n]: https://github.com/intlify/vue-i18n-next
+18
View File
@@ -0,0 +1,18 @@
//#region \0rolldown/runtime.js
var __defProp = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
let target = {};
for (var name in all) {
__defProp(target, name, {
get: all[name],
enumerable: true
});
}
if (!no_symbols) {
__defProp(target, Symbol.toStringTag, { value: "Module" });
}
return target;
};
//#endregion
export { __exportAll as t };
@@ -0,0 +1 @@
export { };
@@ -0,0 +1,10 @@
import { runAsWorker } from "synckit";
import { ESLint } from "eslint";
//#region lib/utils/get-auto-jsonc-rules-config/get-auto-jsonc-rules-config-worker.ts
runAsWorker(async (cwd, fileName) => {
return { rules: (await new ESLint({ cwd }).calculateConfigForFile(fileName)).rules };
});
//#endregion
export { };
+62
View File
@@ -0,0 +1,62 @@
import { a as JSONCSourceCode, i as JSONCComment, n as JSONCLanguage, o as JSONCToken, r as JSONCLanguageOptions, t as RuleModule } from "./types-DS229oMx.mjs";
import { Linter } from "eslint";
//#region lib/meta.d.ts
declare namespace meta_d_exports {
export { name, version };
}
declare const name: "eslint-plugin-jsonc";
declare const version: "3.1.2";
//#endregion
//#region lib/index.d.ts
declare const configs: {
base: Linter.Config[];
"recommended-with-json": Linter.Config[];
"recommended-with-jsonc": Linter.Config[];
"recommended-with-json5": Linter.Config[];
prettier: Linter.Config[];
all: Linter.Config[];
"flat/base": Linter.Config[];
"flat/recommended-with-json": Linter.Config[];
"flat/recommended-with-jsonc": Linter.Config[];
"flat/recommended-with-json5": Linter.Config[];
"flat/prettier": Linter.Config[];
"flat/all": Linter.Config[];
};
declare const rules: {
[key: string]: RuleModule<unknown[]>;
};
declare const languages: {
json: JSONCLanguage;
jsonc: JSONCLanguage;
json5: JSONCLanguage;
x: JSONCLanguage;
};
declare const _default: {
meta: typeof meta_d_exports;
configs: {
base: Linter.Config[];
"recommended-with-json": Linter.Config[];
"recommended-with-jsonc": Linter.Config[];
"recommended-with-json5": Linter.Config[];
prettier: Linter.Config[];
all: Linter.Config[];
"flat/base": Linter.Config[];
"flat/recommended-with-json": Linter.Config[];
"flat/recommended-with-jsonc": Linter.Config[];
"flat/recommended-with-json5": Linter.Config[];
"flat/prettier": Linter.Config[];
"flat/all": Linter.Config[];
};
rules: {
[key: string]: RuleModule<unknown[]>;
};
languages: {
json: JSONCLanguage;
jsonc: JSONCLanguage;
json5: JSONCLanguage;
x: JSONCLanguage;
};
};
//#endregion
export { type JSONCComment, type JSONCLanguageOptions, type JSONCSourceCode, type JSONCToken, configs, _default as default, languages, meta_d_exports as meta, rules };
+587
View File
@@ -0,0 +1,587 @@
import { t as __exportAll } from "./chunk-DQk6qfdC.mjs";
import "./no-parsing-error-B9_Ixkn3.mjs";
import { getRules } from "./rules.mjs";
import { VisitorKeys, parseForESLint, traverseNodes } from "jsonc-eslint-parser";
import { CallMethodStep, ConfigCommentParser, Directive, TextSourceCodeBase, VisitNodeStep } from "@eslint/plugin-kit";
import { TokenStore } from "@ota-meshi/ast-token-store";
//#region lib/configs/flat/base.ts
var base_default = [{ plugins: { get jsonc() {
return lib_default;
} } }, {
files: [
"*.json",
"**/*.json",
"*.json5",
"**/*.json5",
"*.jsonc",
"**/*.jsonc"
],
language: "jsonc/x",
rules: {
strict: "off",
"no-unused-expressions": "off",
"no-unused-vars": "off"
}
}];
//#endregion
//#region lib/configs/flat/recommended-with-json.ts
var recommended_with_json_default = [...base_default, { rules: {
"jsonc/comma-dangle": "error",
"jsonc/no-bigint-literals": "error",
"jsonc/no-binary-expression": "error",
"jsonc/no-binary-numeric-literals": "error",
"jsonc/no-comments": "error",
"jsonc/no-dupe-keys": "error",
"jsonc/no-escape-sequence-in-identifier": "error",
"jsonc/no-floating-decimal": "error",
"jsonc/no-hexadecimal-numeric-literals": "error",
"jsonc/no-infinity": "error",
"jsonc/no-irregular-whitespace": "error",
"jsonc/no-multi-str": "error",
"jsonc/no-nan": "error",
"jsonc/no-number-props": "error",
"jsonc/no-numeric-separators": "error",
"jsonc/no-octal-numeric-literals": "error",
"jsonc/no-octal": "error",
"jsonc/no-parenthesized": "error",
"jsonc/no-plus-sign": "error",
"jsonc/no-regexp-literals": "error",
"jsonc/no-sparse-arrays": "error",
"jsonc/no-template-literals": "error",
"jsonc/no-undefined-value": "error",
"jsonc/no-unicode-codepoint-escapes": "error",
"jsonc/no-useless-escape": "error",
"jsonc/quote-props": "error",
"jsonc/quotes": "error",
"jsonc/space-unary-ops": "error",
"jsonc/valid-json-number": "error",
"jsonc/vue-custom-block/no-parsing-error": "error"
} }];
//#endregion
//#region lib/configs/flat/recommended-with-jsonc.ts
var recommended_with_jsonc_default = [...base_default, { rules: {
"jsonc/no-bigint-literals": "error",
"jsonc/no-binary-expression": "error",
"jsonc/no-binary-numeric-literals": "error",
"jsonc/no-dupe-keys": "error",
"jsonc/no-escape-sequence-in-identifier": "error",
"jsonc/no-floating-decimal": "error",
"jsonc/no-hexadecimal-numeric-literals": "error",
"jsonc/no-infinity": "error",
"jsonc/no-irregular-whitespace": "error",
"jsonc/no-multi-str": "error",
"jsonc/no-nan": "error",
"jsonc/no-number-props": "error",
"jsonc/no-numeric-separators": "error",
"jsonc/no-octal-numeric-literals": "error",
"jsonc/no-octal": "error",
"jsonc/no-parenthesized": "error",
"jsonc/no-plus-sign": "error",
"jsonc/no-regexp-literals": "error",
"jsonc/no-sparse-arrays": "error",
"jsonc/no-template-literals": "error",
"jsonc/no-undefined-value": "error",
"jsonc/no-unicode-codepoint-escapes": "error",
"jsonc/no-useless-escape": "error",
"jsonc/quote-props": "error",
"jsonc/quotes": "error",
"jsonc/space-unary-ops": "error",
"jsonc/valid-json-number": "error",
"jsonc/vue-custom-block/no-parsing-error": "error"
} }];
//#endregion
//#region lib/configs/flat/recommended-with-json5.ts
var recommended_with_json5_default = [...base_default, { rules: {
"jsonc/no-bigint-literals": "error",
"jsonc/no-binary-expression": "error",
"jsonc/no-binary-numeric-literals": "error",
"jsonc/no-dupe-keys": "error",
"jsonc/no-escape-sequence-in-identifier": "error",
"jsonc/no-irregular-whitespace": "error",
"jsonc/no-number-props": "error",
"jsonc/no-numeric-separators": "error",
"jsonc/no-octal-numeric-literals": "error",
"jsonc/no-octal": "error",
"jsonc/no-parenthesized": "error",
"jsonc/no-regexp-literals": "error",
"jsonc/no-sparse-arrays": "error",
"jsonc/no-template-literals": "error",
"jsonc/no-undefined-value": "error",
"jsonc/no-unicode-codepoint-escapes": "error",
"jsonc/no-useless-escape": "error",
"jsonc/space-unary-ops": "error",
"jsonc/vue-custom-block/no-parsing-error": "error"
} }];
//#endregion
//#region lib/configs/flat/prettier.ts
var prettier_default = [...base_default, { rules: {
"jsonc/array-bracket-newline": "off",
"jsonc/array-bracket-spacing": "off",
"jsonc/array-element-newline": "off",
"jsonc/comma-dangle": "off",
"jsonc/comma-style": "off",
"jsonc/indent": "off",
"jsonc/key-spacing": "off",
"jsonc/no-floating-decimal": "off",
"jsonc/object-curly-newline": "off",
"jsonc/object-curly-spacing": "off",
"jsonc/object-property-newline": "off",
"jsonc/quote-props": "off",
"jsonc/quotes": "off",
"jsonc/space-unary-ops": "off"
} }];
//#endregion
//#region lib/configs/flat/all.ts
const all = {};
for (const rule of getRules()) {
if (rule.meta.docs.ruleId === "jsonc/sort-array-values") continue;
all[rule.meta.docs.ruleId] = "error";
}
const config = [...base_default, { rules: { ...all } }];
//#endregion
//#region lib/meta.ts
var meta_exports = /* @__PURE__ */ __exportAll({
name: () => name,
version: () => version
});
const name = "eslint-plugin-jsonc";
const version = "3.1.2";
//#endregion
//#region lib/language/jsonc-source-code.ts
/**
* @fileoverview The JSONCSourceCode class.
*/
const commentParser = new ConfigCommentParser();
/**
* Pattern to match ESLint inline configuration comments in JSONC.
* Matches: eslint, eslint-disable, eslint-enable, eslint-disable-line, eslint-disable-next-line
*/
const INLINE_CONFIG = /^\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|$)/u;
/**
* JSONC Source Code Object
*/
var JSONCSourceCode = class extends TextSourceCodeBase {
hasBOM;
parserServices;
visitorKeys;
tokenStore;
#steps = null;
#cacheTokensAndComments = null;
#inlineConfigComments = null;
/**
* Creates a new instance.
*/
constructor(config) {
super({
ast: config.ast,
text: config.text
});
this.hasBOM = Boolean(config.hasBOM);
this.parserServices = config.parserServices;
this.visitorKeys = config.visitorKeys || VisitorKeys;
this.tokenStore = new TokenStore({
tokens: [...config.ast.tokens, ...config.ast.comments],
isComment: (token) => token.type === "Block" || token.type === "Line"
});
}
traverse() {
if (this.#steps != null) return this.#steps;
const steps = [];
this.#steps = steps;
const root = this.ast;
steps.push(new CallMethodStep({
target: "onCodePathStart",
args: [{}, root]
}));
traverseNodes(root, {
enterNode(n) {
steps.push(new VisitNodeStep({
target: n,
phase: 1,
args: [n]
}));
},
leaveNode(n) {
steps.push(new VisitNodeStep({
target: n,
phase: 2,
args: [n]
}));
}
});
steps.push(new CallMethodStep({
target: "onCodePathEnd",
args: [{}, root]
}));
return steps;
}
/**
* Gets all tokens and comments.
*/
get tokensAndComments() {
return this.#cacheTokensAndComments ??= [...this.ast.tokens, ...this.ast.comments].sort((a, b) => a.range[0] - b.range[0]);
}
getLines() {
return this.lines;
}
getAllComments() {
return this.tokenStore.getAllComments();
}
/**
* Returns an array of all inline configuration nodes found in the source code.
* This includes eslint-disable, eslint-enable, eslint-disable-line,
* eslint-disable-next-line, and eslint (for inline config) comments.
*/
getInlineConfigNodes() {
if (!this.#inlineConfigComments) this.#inlineConfigComments = this.ast.comments.filter((comment) => INLINE_CONFIG.test(comment.value));
return this.#inlineConfigComments;
}
/**
* Returns directives that enable or disable rules along with any problems
* encountered while parsing the directives.
*/
getDisableDirectives() {
const problems = [];
const directives = [];
this.getInlineConfigNodes().forEach((comment) => {
const directive = commentParser.parseDirective(comment.value);
if (!directive) return;
const { label, value, justification } = directive;
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(7);
directives.push(new Directive({
type: directiveType,
node: comment,
value,
justification
}));
break;
}
}
});
return {
problems,
directives
};
}
/**
* Returns inline rule configurations along with any problems
* encountered while parsing the configurations.
*/
applyInlineConfig() {
const problems = [];
const configs = [];
this.getInlineConfigNodes().forEach((comment) => {
const directive = commentParser.parseDirective(comment.value);
if (!directive) return;
const { label, value } = directive;
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: parseResult.error.message,
loc: comment.loc
});
}
});
return {
configs,
problems
};
}
/**
* Gets the source text for the given node or the entire source if no node is provided.
*/
getText(node, beforeCount, afterCount) {
if (!node) return this.text;
const range = node.range;
const start = range[0] - (beforeCount ?? 0);
const end = range[1] + (afterCount ?? 0);
return this.text.slice(Math.max(0, start), Math.min(this.text.length, end));
}
getNodeByRangeIndex(index) {
let node = find([this.ast]);
if (!node) return null;
while (true) {
const child = find(this._getChildren(node));
if (!child) return node;
node = child;
}
/**
* Finds a node that contains the given index.
*/
function find(nodes) {
for (const node of nodes) if (node.range[0] <= index && index < node.range[1]) return node;
return null;
}
}
getFirstToken(node, options) {
return this.tokenStore.getFirstToken(node, options);
}
getFirstTokens(node, options) {
return this.tokenStore.getFirstTokens(node, options);
}
getLastToken(node, options) {
return this.tokenStore.getLastToken(node, options);
}
getLastTokens(node, options) {
return this.tokenStore.getLastTokens(node, options);
}
getTokenBefore(node, options) {
return this.tokenStore.getTokenBefore(node, options);
}
getTokensBefore(node, options) {
return this.tokenStore.getTokensBefore(node, options);
}
getTokenAfter(node, options) {
return this.tokenStore.getTokenAfter(node, options);
}
getTokensAfter(node, options) {
return this.tokenStore.getTokensAfter(node, options);
}
getFirstTokenBetween(left, right, options) {
return this.tokenStore.getFirstTokenBetween(left, right, options);
}
getFirstTokensBetween(left, right, options) {
return this.tokenStore.getFirstTokensBetween(left, right, options);
}
getLastTokenBetween(left, right, options) {
return this.tokenStore.getLastTokenBetween(left, right, options);
}
getLastTokensBetween(left, right, options) {
return this.tokenStore.getLastTokensBetween(left, right, options);
}
getTokens(node, options) {
return this.tokenStore.getTokens(node, options);
}
getTokensBetween(left, right, options) {
return this.tokenStore.getTokensBetween(left, right, options);
}
getCommentsInside(nodeOrToken) {
return this.tokenStore.getCommentsInside(nodeOrToken);
}
getCommentsBefore(nodeOrToken) {
return this.tokenStore.getCommentsBefore(nodeOrToken);
}
getCommentsAfter(nodeOrToken) {
return this.tokenStore.getCommentsAfter(nodeOrToken);
}
commentsExistBetween(first, second) {
return this.tokenStore.commentsExistBetween(first, second);
}
isSpaceBetween(first, second) {
const [left, right] = first.range[1] <= second.range[0] ? [first, second] : [second, first];
return this.tokenStore.isSpaceBetween(left, right);
}
/**
* Compatibility for ESLint's SourceCode API
* @deprecated JSONC does not have scopes
*/
getScope(node) {
if (node?.type !== "Program") return null;
return createFakeGlobalScope(this.ast);
}
/**
* Compatibility for ESLint's SourceCode API
* @deprecated JSONC does not have scopes
*/
get scopeManager() {
return {
scopes: [],
globalScope: createFakeGlobalScope(this.ast),
acquire: (node) => {
if (node.type === "Program") return createFakeGlobalScope(this.ast);
return null;
},
getDeclaredVariables: () => [],
addGlobals: () => {}
};
}
/**
* Compatibility for ESLint's SourceCode API
* @deprecated
*/
isSpaceBetweenTokens(first, second) {
return this.isSpaceBetween(first, second);
}
_getChildren(node) {
const keys = this.visitorKeys[node.type] || [];
const children = [];
for (const key of keys) {
const value = node[key];
if (Array.isArray(value)) {
for (const element of value) if (isNode(element)) children.push(element);
} else if (isNode(value)) children.push(value);
}
return children;
}
};
/**
* Determines whether the given value is a JSONC AST node.
*/
function isNode(value) {
return typeof value === "object" && value !== null && typeof value.type === "string" && Array.isArray(value.range) && Boolean(value.loc) && typeof value.loc === "object";
}
/**
* Creates a fake global scope for JSONC files.
* @deprecated JSONC does not have scopes
*/
function createFakeGlobalScope(node) {
const fakeGlobalScope = {
type: "global",
block: node,
set: /* @__PURE__ */ new Map(),
through: [],
childScopes: [],
variableScope: null,
variables: [],
references: [],
functionExpressionScope: false,
isStrict: false,
upper: null,
implicit: {
variables: [],
set: /* @__PURE__ */ new Map()
}
};
fakeGlobalScope.variableScope = fakeGlobalScope;
return fakeGlobalScope;
}
//#endregion
//#region lib/language/jsonc-language.ts
/**
* The JSONC language implementation for ESLint.
*/
var JSONCLanguage = class {
/**
* The type of file to read.
*/
fileType = "text";
/**
* The line number at which the parser starts counting.
*/
lineStart = 1;
/**
* The column number at which the parser starts counting.
*/
columnStart = 0;
/**
* The name of the key that holds the type of the node.
*/
nodeTypeKey = "type";
_mode;
constructor(options) {
this._mode = options?.mode ?? "EXTENDED";
}
/**
* Validates the language options.
*/
validateLanguageOptions(_languageOptions) {}
normalizeLanguageOptions(languageOptions) {
return {
ecmaVersion: "latest",
...languageOptions,
parserOptions: {
...this._mode !== "EXTENDED" ? { jsonSyntax: this._mode } : {},
...languageOptions.parserOptions
}
};
}
/**
* Parses the given file into an AST.
*/
parse(file, context) {
const text = file.body;
try {
return {
ok: true,
ast: parseForESLint(text, { jsonSyntax: context.languageOptions?.parserOptions?.jsonSyntax ?? (this._mode !== "EXTENDED" ? this._mode : void 0) }).ast
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const parseError = error;
return {
ok: false,
errors: [{
message,
line: parseError.lineNumber ?? 1,
column: parseError.column ?? 1
}]
};
}
}
/**
* Creates a new SourceCode object for the given file and parse result.
*/
createSourceCode(file, parseResult) {
return new JSONCSourceCode({
text: file.body,
ast: parseResult.ast,
hasBOM: file.bom,
parserServices: { isJSON: true },
visitorKeys: VisitorKeys
});
}
};
//#endregion
//#region lib/index.ts
const configs = {
base: base_default,
"recommended-with-json": recommended_with_json_default,
"recommended-with-jsonc": recommended_with_jsonc_default,
"recommended-with-json5": recommended_with_json5_default,
prettier: prettier_default,
all: config,
"flat/base": base_default,
"flat/recommended-with-json": recommended_with_json_default,
"flat/recommended-with-jsonc": recommended_with_jsonc_default,
"flat/recommended-with-json5": recommended_with_json5_default,
"flat/prettier": prettier_default,
"flat/all": config
};
const rules = getRules().reduce((obj, r) => {
obj[r.meta.docs.ruleName] = r;
return obj;
}, {});
const languages = {
json: new JSONCLanguage({ mode: "JSON" }),
jsonc: new JSONCLanguage({ mode: "JSONC" }),
json5: new JSONCLanguage({ mode: "JSON5" }),
x: new JSONCLanguage({ mode: "EXTENDED" })
};
var lib_default = {
meta: meta_exports,
configs,
rules,
languages
};
//#endregion
export { configs, lib_default as default, languages, meta_exports as meta, rules };
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
import { t as RuleModule } from "./types-DS229oMx.mjs";
//#region lib/utils/rules.d.ts
/**
*
*/
declare function getRules(): RuleModule[];
//#endregion
export { getRules };
+60
View File
@@ -0,0 +1,60 @@
import { A as no_escape_sequence_in_identifier_default, B as comma_dangle_default, C as no_number_props_default, D as no_infinity_default, E as no_irregular_whitespace_default, F as no_bigint_literals_default, H as array_element_newline_default, I as key_spacing_default, L as key_name_casing_default, M as no_comments_default, N as no_binary_numeric_literals_default, O as no_hexadecimal_numeric_literals_default, P as no_binary_expression_default, R as indent_default, S as no_numeric_separators_default, T as no_multi_str_default, U as array_bracket_spacing_default, V as auto_default, W as array_bracket_newline_default, _ as no_plus_sign_default, a as sort_array_values_default, b as no_octal_numeric_literals_default, c as object_property_newline_default, d as no_useless_escape_default, f as no_unicode_codepoint_escapes_default, g as no_regexp_literals_default, h as no_sparse_arrays_default, i as sort_keys_default, j as no_dupe_keys_default, k as no_floating_decimal_default, l as object_curly_spacing_default, m as no_template_literals_default, n as valid_json_number_default, o as quotes_default, p as no_undefined_value_default, r as space_unary_ops_default, s as quote_props_default, t as no_parsing_error_default, u as object_curly_newline_default, v as no_parenthesized_default, w as no_nan_default, x as no_octal_escape_default, y as no_octal_default, z as comma_style_default } from "./no-parsing-error-B9_Ixkn3.mjs";
//#region lib/utils/rules.ts
let rules = null;
/**
*
*/
function getRules() {
if (rules) return rules;
rules = [
array_bracket_newline_default,
array_bracket_spacing_default,
array_element_newline_default,
auto_default,
comma_dangle_default,
comma_style_default,
indent_default,
key_name_casing_default,
key_spacing_default,
no_bigint_literals_default,
no_binary_expression_default,
no_binary_numeric_literals_default,
no_comments_default,
no_dupe_keys_default,
no_escape_sequence_in_identifier_default,
no_floating_decimal_default,
no_hexadecimal_numeric_literals_default,
no_infinity_default,
no_irregular_whitespace_default,
no_multi_str_default,
no_nan_default,
no_number_props_default,
no_numeric_separators_default,
no_octal_escape_default,
no_octal_numeric_literals_default,
no_octal_default,
no_parenthesized_default,
no_plus_sign_default,
no_regexp_literals_default,
no_sparse_arrays_default,
no_template_literals_default,
no_undefined_value_default,
no_unicode_codepoint_escapes_default,
no_useless_escape_default,
object_curly_newline_default,
object_curly_spacing_default,
object_property_newline_default,
quote_props_default,
quotes_default,
sort_array_values_default,
sort_keys_default,
space_unary_ops_default,
valid_json_number_default,
no_parsing_error_default
];
return rules;
}
//#endregion
export { getRules };
+375
View File
@@ -0,0 +1,375 @@
import { AST, JSONParserOptions, RuleListener } from "jsonc-eslint-parser";
import { IDirective, TextSourceCodeBase, TraversalStep } from "@eslint/plugin-kit";
import { CursorWithCountOptionsWithComment, CursorWithCountOptionsWithFilter, CursorWithCountOptionsWithoutFilter, CursorWithSkipOptionsWithComment, CursorWithSkipOptionsWithFilter, CursorWithSkipOptionsWithoutFilter } from "@ota-meshi/ast-token-store";
import { AST as AST$1, Scope } from "eslint";
import * as core from "@eslint/core";
import { File, FileProblem, Language, NotOkParseResult, OkParseResult, RulesConfig } from "@eslint/core";
import { Comment } from "estree";
//#region lib/language/jsonc-source-code.d.ts
/**
* A comment token with required range and loc.
*/
type JSONCComment = Comment & {
range: [number, number];
loc: AST.SourceLocation;
};
/**
* JSONC-specific syntax element type
*/
type JSONCSyntaxElement = AST.JSONNode | JSONCTokenOrComment;
type JSONCToken = AST$1.Token;
type JSONCTokenOrComment = JSONCToken | JSONCComment;
/**
* JSONC Source Code Object
*/
declare class JSONCSourceCode extends TextSourceCodeBase<{
LangOptions: Record<never, never>;
RootNode: AST.JSONProgram;
SyntaxElementWithLoc: JSONCSyntaxElement;
ConfigNode: JSONCComment;
}> {
#private;
readonly hasBOM: boolean;
readonly parserServices: {
isJSON?: boolean;
parseError?: unknown;
};
readonly visitorKeys: Record<string, string[]>;
private readonly tokenStore;
/**
* Creates a new instance.
*/
constructor(config: {
text: string;
ast: AST.JSONProgram;
hasBOM: boolean;
parserServices: {
isJSON: boolean;
parseError?: unknown;
};
visitorKeys?: Record<string, string[]> | null | undefined;
});
traverse(): Iterable<TraversalStep>;
/**
* Gets all tokens and comments.
*/
get tokensAndComments(): JSONCTokenOrComment[];
getLines(): string[];
getAllComments(): JSONCComment[];
/**
* Returns an array of all inline configuration nodes found in the source code.
* This includes eslint-disable, eslint-enable, eslint-disable-line,
* eslint-disable-next-line, and eslint (for inline config) comments.
*/
getInlineConfigNodes(): JSONCComment[];
/**
* Returns directives that enable or disable rules along with any problems
* encountered while parsing the directives.
*/
getDisableDirectives(): {
directives: IDirective[];
problems: FileProblem[];
};
/**
* Returns inline rule configurations along with any problems
* encountered while parsing the configurations.
*/
applyInlineConfig(): {
configs: {
config: {
rules: RulesConfig;
};
loc: AST.SourceLocation;
}[];
problems: FileProblem[];
};
/**
* Gets the source text for the given node or the entire source if no node is provided.
*/
getText(node?: JSONCSyntaxElement, beforeCount?: number, afterCount?: number): string;
getNodeByRangeIndex(index: number): AST.JSONNode | null;
/**
* Gets the first token of the given node.
*/
getFirstToken(node: JSONCSyntaxElement): JSONCToken;
/**
* Gets the first token of the given node with options.
*/
getFirstToken(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithoutFilter): JSONCToken | null;
/**
* Gets the first token of the given node with filter options.
*/
getFirstToken<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithFilter<JSONCToken, R>): R | null;
/**
* Gets the first token of the given node with comment options.
*/
getFirstToken<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>): R | null;
/**
* Gets the first tokens of the given node.
*/
getFirstTokens(node: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Gets the first tokens of the given node with filter options.
*/
getFirstTokens<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Gets the first tokens of the given node with comment options.
*/
getFirstTokens<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
/**
* Gets the last token of the given node.
*/
getLastToken(node: JSONCSyntaxElement): JSONCToken;
/**
* Gets the last token of the given node with options.
*/
getLastToken(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithoutFilter): JSONCToken | null;
/**
* Gets the last token of the given node with filter options.
*/
getLastToken<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithFilter<JSONCToken, R>): R | null;
/**
* Gets the last token of the given node with comment options.
*/
getLastToken<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>): R | null;
/**
* Get the last tokens of the given node.
*/
getLastTokens(node: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Get the last tokens of the given node with filter options.
*/
getLastTokens<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Get the last tokens of the given node with comment options.
*/
getLastTokens<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
/**
* Gets the token that precedes a given node or token.
*/
getTokenBefore(node: JSONCSyntaxElement, options?: CursorWithSkipOptionsWithoutFilter): JSONCToken | null;
/**
* Gets the token that precedes a given node or token with filter options.
*/
getTokenBefore<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithFilter<JSONCToken, R>): R | null;
/**
* Gets the token that precedes a given node or token with comment options.
*/
getTokenBefore<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>): R | null;
/**
* Gets the `count` tokens that precedes a given node or token.
*/
getTokensBefore(node: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Gets the `count` tokens that precedes a given node or token with filter options.
*/
getTokensBefore<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Gets the `count` tokens that precedes a given node or token with comment options.
*/
getTokensBefore<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
/**
* Gets the token that follows a given node or token.
*/
getTokenAfter(node: JSONCSyntaxElement, options?: CursorWithSkipOptionsWithoutFilter): JSONCToken | null;
/**
* Gets the token that follows a given node or token with filter options.
*/
getTokenAfter<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithFilter<JSONCToken, R>): R | null;
/**
* Gets the token that follows a given node or token with comment options.
*/
getTokenAfter<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>): R | null;
/**
* Gets the `count` tokens that follows a given node or token.
*/
getTokensAfter(node: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Gets the `count` tokens that follows a given node or token with filter options.
*/
getTokensAfter<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Gets the `count` tokens that follows a given node or token with comment options.
*/
getTokensAfter<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
/**
* Gets the first token between two non-overlapping nodes.
*/
getFirstTokenBetween(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options?: CursorWithSkipOptionsWithoutFilter): JSONCToken | null;
/**
* Gets the first token between two non-overlapping nodes with filter options.
*/
getFirstTokenBetween<R extends JSONCToken>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithSkipOptionsWithFilter<JSONCToken, R>): R | null;
/**
* Gets the first token between two non-overlapping nodes with comment options.
*/
getFirstTokenBetween<R extends JSONCToken | JSONCComment>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>): R | null;
/**
* Gets the first tokens between two non-overlapping nodes.
*/
getFirstTokensBetween(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Gets the first tokens between two non-overlapping nodes with filter options.
*/
getFirstTokensBetween<R extends JSONCToken>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Gets the first tokens between two non-overlapping nodes with comment options.
*/
getFirstTokensBetween<R extends JSONCToken | JSONCComment>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
/**
* Gets the last token between two non-overlapping nodes.
*/
getLastTokenBetween(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options?: CursorWithSkipOptionsWithoutFilter): JSONCToken | null;
/**
* Gets the last token between two non-overlapping nodes with filter options.
*/
getLastTokenBetween<R extends JSONCToken>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithSkipOptionsWithFilter<JSONCToken, R>): R | null;
/**
* Gets the last token between two non-overlapping nodes with comment options.
*/
getLastTokenBetween<R extends JSONCToken | JSONCComment>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithSkipOptionsWithComment<JSONCToken, JSONCComment, R>): R | null;
/**
* Gets the last tokens between two non-overlapping nodes.
*/
getLastTokensBetween(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Gets the last tokens between two non-overlapping nodes with filter options.
*/
getLastTokensBetween<R extends JSONCToken>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Gets the last tokens between two non-overlapping nodes with comment options.
*/
getLastTokensBetween<R extends JSONCToken | JSONCComment>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
/**
* Gets all tokens that are related to the given node.
*/
getTokens(node: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Gets all tokens that are related to the given node with filter options.
*/
getTokens<R extends JSONCToken>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Gets all tokens that are related to the given node with comment options.
*/
getTokens<R extends JSONCToken | JSONCComment>(node: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
/**
* Gets all of the tokens between two non-overlapping nodes.
*/
getTokensBetween(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options?: CursorWithCountOptionsWithoutFilter): JSONCToken[];
/**
* Gets all of the tokens between two non-overlapping nodes with filter options.
*/
getTokensBetween<R extends JSONCToken>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithCountOptionsWithFilter<JSONCToken, R>): R[];
/**
* Gets all of the tokens between two non-overlapping nodes with comment options.
*/
getTokensBetween<R extends JSONCToken | JSONCComment>(left: JSONCSyntaxElement, right: JSONCSyntaxElement, options: CursorWithCountOptionsWithComment<JSONCToken, JSONCComment, R>): R[];
getCommentsInside(nodeOrToken: JSONCSyntaxElement): JSONCComment[];
getCommentsBefore(nodeOrToken: JSONCSyntaxElement): JSONCComment[];
getCommentsAfter(nodeOrToken: JSONCSyntaxElement): JSONCComment[];
commentsExistBetween(first: JSONCSyntaxElement, second: JSONCSyntaxElement): boolean;
isSpaceBetween(first: JSONCToken | JSONCComment, second: JSONCToken | JSONCComment): boolean;
/**
* Compatibility for ESLint's SourceCode API
* @deprecated JSONC does not have scopes
*/
getScope(node?: AST.JSONNode): Scope.Scope | null;
/**
* Compatibility for ESLint's SourceCode API
* @deprecated JSONC does not have scopes
*/
get scopeManager(): Scope.ScopeManager | null;
/**
* Compatibility for ESLint's SourceCode API
* @deprecated
*/
isSpaceBetweenTokens(first: JSONCTokenOrComment, second: JSONCTokenOrComment): boolean;
private _getChildren;
}
//#endregion
//#region lib/language/jsonc-language.d.ts
/**
* Language options for JSONC.
*/
type JSONCLanguageOptions = {
parserOptions?: JSONParserOptions;
};
type ParserMode = "JSON" | "JSONC" | "JSON5" | "EXTENDED";
type JSONCLanguageInstanceOptions = {
mode?: ParserMode;
};
/**
* The JSONC language implementation for ESLint.
*/
declare class JSONCLanguage implements Language<{
LangOptions: JSONCLanguageOptions;
Code: JSONCSourceCode;
RootNode: AST.JSONProgram;
Node: AST.JSONNode;
}> {
/**
* The type of file to read.
*/
fileType: "text";
/**
* The line number at which the parser starts counting.
*/
lineStart: 1;
/**
* The column number at which the parser starts counting.
*/
columnStart: 0;
/**
* The name of the key that holds the type of the node.
*/
nodeTypeKey: "type";
private readonly _mode;
constructor(options?: JSONCLanguageInstanceOptions);
/**
* Validates the language options.
*/
validateLanguageOptions(_languageOptions: JSONCLanguageOptions): void;
normalizeLanguageOptions(languageOptions: JSONCLanguageOptions): JSONCLanguageOptions;
/**
* Parses the given file into an AST.
*/
parse(file: File, context: {
languageOptions?: JSONCLanguageOptions;
}): OkParseResult<AST.JSONProgram> | NotOkParseResult;
/**
* Creates a new SourceCode object for the given file and parse result.
*/
createSourceCode(file: File, parseResult: OkParseResult<AST.JSONProgram>): JSONCSourceCode;
}
//#endregion
//#region lib/types.d.ts
interface RuleModule<RuleOptions extends unknown[] = unknown[]> extends core.RuleDefinition<{
LangOptions: JSONCLanguageOptions;
Code: JSONCSourceCode;
RuleOptions: RuleOptions;
Visitor: RuleListener;
Node: AST.JSONNode;
MessageIds: string;
ExtRuleDocs: RuleMetaDocs;
}> {
meta: RuleMetaData<RuleOptions>;
}
type RuleMetaDocs = {
description: string;
recommended: ("json" | "jsonc" | "json5")[] | null;
url: string;
ruleId: string;
ruleName: string;
default?: "error" | "warn";
extensionRule: boolean | string | {
plugin: string;
url: string;
};
layout: boolean;
};
interface RuleMetaData<RuleOptions extends unknown[] = unknown[]> extends core.RulesMeta<string, RuleOptions, RuleMetaDocs> {
docs: RuleMetaDocs;
}
//#endregion
export { JSONCSourceCode as a, JSONCComment as i, JSONCLanguage as n, JSONCToken as o, JSONCLanguageOptions as r, RuleModule as t };
@@ -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.
@@ -0,0 +1,29 @@
# ESLint Core
## Overview
This package is the future home of the rewritten, runtime-agnostic ESLint core.
Right now, it exports the core types necessary to implement language plugins.
## 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></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="#"><img src="https://images.opencollective.com/aeriusventilations-org/avatar.png" alt="aeriusventilation's Org" 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://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="TestMu AI Open Source Office (Formerly LambdaTest)" 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-->
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
{
"name": "@eslint/core",
"version": "1.2.1",
"description": "Runtime-agnostic core of ESLint",
"type": "module",
"types": "./dist/esm/types.d.ts",
"exports": {
"types": {
"import": "./dist/esm/types.d.ts",
"require": "./dist/cjs/types.d.cts"
}
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"build:cts": "node -e \"fs.cpSync('dist/esm/types.d.ts', 'dist/cjs/types.d.cts')\"",
"build": "tsc && npm run build:cts",
"lint:types": "attw --pack",
"pretest": "npm run build",
"test": "npm run test:types",
"test:jsr": "npx -y jsr@latest publish --dry-run",
"test:types": "tsc -p tests/types/tsconfig.json"
},
"repository": {
"type": "git",
"url": "git+https://github.com/eslint/rewrite.git",
"directory": "packages/core"
},
"keywords": [
"eslint",
"core"
],
"author": "Nicholas C. Zakas",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/eslint/rewrite/issues"
},
"homepage": "https://github.com/eslint/rewrite/tree/main/packages/core#readme",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"devDependencies": {
"json-schema": "^0.4.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
}
@@ -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.
@@ -0,0 +1,273 @@
# ESLint Plugin Kit
## Description
A collection of utilities to help build ESLint plugins.
## Installation
For Node.js and compatible runtimes:
```shell
npm install @eslint/plugin-kit
# or
yarn add @eslint/plugin-kit
# or
pnpm install @eslint/plugin-kit
# or
bun add @eslint/plugin-kit
```
For Deno:
```shell
deno add @eslint/plugin-kit
```
## Usage
This package exports the following utilities:
- [`ConfigCommentParser`](#configcommentparser) - used to parse ESLint configuration comments (i.e., `/* eslint-disable rule */`)
- [`VisitNodeStep` and `CallMethodStep`](#visitnodestep-and-callmethodstep) - used to help implement `SourceCode#traverse()`
- [`Directive`](#directive) - used to help implement `SourceCode#getDisableDirectives()`
- [`TextSourceCodeBase`](#textsourcecodebase) - base class to help implement the `SourceCode` interface
### `ConfigCommentParser`
To use the `ConfigCommentParser` class, import it from the package and create a new instance, such as:
```js
import { ConfigCommentParser } from "@eslint/plugin-kit";
// create a new instance
const commentParser = new ConfigCommentParser();
// pass in a comment string without the comment delimiters
const directive = commentParser.parseDirective(
"eslint-disable prefer-const, no-var -- I don't want to use these.",
);
// will be undefined when a directive can't be parsed
if (directive) {
console.log(directive.label); // "eslint-disable"
console.log(directive.value); // "prefer-const, no-var"
console.log(directive.justification); // "I don't want to use these."
}
```
There are different styles of directive values that you'll need to parse separately to get the correct format:
```js
import { ConfigCommentParser } from "@eslint/plugin-kit";
// create a new instance
const commentParser = new ConfigCommentParser();
// list format
const list = commentParser.parseListConfig("prefer-const, no-var");
console.log(Object.entries(list)); // [["prefer-const", true], ["no-var", true]]
// string format
const strings = commentParser.parseStringConfig("foo:off, bar");
console.log(Object.entries(strings)); // [["foo", "off"], ["bar", null]]
// JSON-like config format
const jsonLike = commentParser.parseJSONLikeConfig(
"radix:[error, always], prefer-const: warn",
);
console.log(Object.entries(jsonLike.config)); // [["radix", ["error", "always"]], ["prefer-const", "warn"]]
```
### `VisitNodeStep` and `CallMethodStep`
The `VisitNodeStep` and `CallMethodStep` classes represent steps in the traversal of source code. They implement the correct interfaces to return from the `SourceCode#traverse()` method.
The `VisitNodeStep` class is the more common of the two, where you are describing a visit to a particular node during the traversal. The constructor accepts three arguments:
- `target` - the node being visited. This is used to determine the method to call inside of a rule. For instance, if the node's type is `Literal` then ESLint will call a method named `Literal()` on the rule (if present).
- `phase` - either 1 for enter or 2 for exit.
- `args` - an array of arguments to pass into the visitor method of a rule.
For example:
```js
import { VisitNodeStep } from "@eslint/plugin-kit";
class MySourceCode {
traverse() {
const steps = [];
for (const { node, parent, phase } of iterator(this.ast)) {
steps.push(
new VisitNodeStep({
target: node,
phase: phase === "enter" ? 1 : 2,
args: [node, parent],
}),
);
}
return steps;
}
}
```
The `CallMethodStep` class is less common and is used to tell ESLint to call a specific method on the rule. The constructor accepts two arguments:
- `target` - the name of the method to call, frequently beginning with `"on"` such as `"onCodePathStart"`.
- `args` - an array of arguments to pass to the method.
For example:
```js
import { VisitNodeStep, CallMethodStep } from "@eslint/plugin-kit";
class MySourceCode {
traverse() {
const steps = [];
for (const { node, parent, phase } of iterator(this.ast)) {
steps.push(
new VisitNodeStep({
target: node,
phase: phase === "enter" ? 1 : 2,
args: [node, parent],
}),
);
// call a method indicating how many times we've been through the loop
steps.push(
new CallMethodStep({
target: "onIteration",
args: [steps.length]
});
)
}
return steps;
}
}
```
### `Directive`
The `Directive` class represents a disable directive in the source code and implements the `Directive` interface from `@eslint/core`. You can tell ESLint about disable directives using the `SourceCode#getDisableDirectives()` method, where part of the return value is an array of `Directive` objects. Here's an example:
```js
import { Directive, ConfigCommentParser } from "@eslint/plugin-kit";
class MySourceCode {
getDisableDirectives() {
const directives = [];
const problems = [];
const commentParser = new ConfigCommentParser();
// read in the inline config nodes to check each one
this.getInlineConfigNodes().forEach(comment => {
// Step 1: Parse the directive
const { label, value, justification } =
commentParser.parseDirective(comment.value);
// Step 2: Extract the directive value and create the `Directive` object
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: directiveType,
node: comment,
value,
justification,
}),
);
}
// ignore any comments that don't begin with known labels
}
});
return {
directives,
problems,
};
}
}
```
### `TextSourceCodeBase`
The `TextSourceCodeBase` class is intended to be a base class that has several of the common members found in `SourceCode` objects already implemented. Those members are:
- `lines` - an array of text lines that is created automatically when the constructor is called.
- `getLoc(nodeOrToken)` - gets the location of a node or token. Works for nodes that have the ESLint-style `loc` property and nodes that have the Unist-style [`position` property](https://github.com/syntax-tree/unist?tab=readme-ov-file#position). If you're using an AST with a different location format, you'll still need to implement this method yourself.
- `getLocFromIndex(index)` - Converts a source text index into a `{ line: number, column: number }` pair. (For this method to work, the root node should always cover the entire source code text, and the `getLoc()` method needs to be implemented correctly.)
- `getIndexFromLoc(loc)` - Converts a `{ line: number, column: number }` pair into a source text index. (For this method to work, the root node should always cover the entire source code text, and the `getLoc()` method needs to be implemented correctly.)
- `getRange(nodeOrToken)` - gets the range of a node or token within the source text. Works for nodes that have the ESLint-style `range` property and nodes that have the Unist-style [`position` property](https://github.com/syntax-tree/unist?tab=readme-ov-file#position). If you're using an AST with a different range format, you'll still need to implement this method yourself.
- `getText(node, beforeCount, afterCount)` - gets the source text for the given node that has range information attached. Optionally, can return additional characters before and after the given node. As long as `getRange()` is properly implemented, this method will just work.
- `getAncestors(node)` - returns the ancestry of the node. In order for this to work, you must implement the `getParent()` method yourself.
Here's an example:
```js
import { TextSourceCodeBase } from "@eslint/plugin-kit";
export class MySourceCode extends TextSourceCodeBase {
#parents = new Map();
constructor({ ast, text }) {
super({ ast, text });
}
getParent(node) {
return this.#parents.get(node);
}
traverse() {
const steps = [];
for (const { node, parent, phase } of iterator(this.ast)) {
//save the parent information
this.#parent.set(node, parent);
steps.push(
new VisitNodeStep({
target: node,
phase: phase === "enter" ? 1 : 2,
args: [node, parent],
}),
);
}
return steps;
}
}
```
In general, it's safe to collect the parent information during the `traverse()` method as `getParent()` and `getAncestor()` will only be called from rules once the AST has been traversed at least once.
## 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></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://www.n-ix.com/"><img src="https://images.opencollective.com/n-ix-ltd/575a7a5/logo.png" alt="N-iX Ltd" 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://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="TestMu AI Open Source Office (Formerly LambdaTest)" 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-->
@@ -0,0 +1,895 @@
'use strict';
var levn = require('levn');
/**
* @fileoverview Config Comment Parser
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/** @import * as $eslintcore from "@eslint/core"; */
/** @typedef {$eslintcore.RuleConfig} RuleConfig */
/** @typedef {$eslintcore.RulesConfig} RulesConfig */
/** @import * as $typests from "./types.ts"; */
/** @typedef {$typests.StringConfig} StringConfig */
/** @typedef {$typests.BooleanConfig} BooleanConfig */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const directivesPattern = /^([a-z]+(?:-[a-z]+)*)(?:\s|$)/u;
const validSeverities = new Set([0, 1, 2, "off", "warn", "error"]);
/**
* Determines if the severity in the rule configuration is valid.
* @param {RuleConfig} ruleConfig A rule's configuration.
* @returns {boolean} `true` if the severity is valid, otherwise `false`.
*/
function isSeverityValid(ruleConfig) {
const severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
return validSeverities.has(severity);
}
/**
* Determines if all severities in the rules configuration are valid.
* @param {RulesConfig} rulesConfig The rules configuration to check.
* @returns {boolean} `true` if all severities are valid, otherwise `false`.
*/
function isEverySeverityValid(rulesConfig) {
return Object.values(rulesConfig).every(isSeverityValid);
}
/**
* Represents a directive comment.
*/
class DirectiveComment {
/**
* The label of the directive, such as "eslint", "eslint-disable", etc.
* @type {string}
*/
label = "";
/**
* The value of the directive (the string after the label).
* @type {string}
*/
value = "";
/**
* The justification of the directive (the string after the --).
* @type {string}
*/
justification = "";
/**
* Creates a new directive comment.
* @param {string} label The label of the directive.
* @param {string} value The value of the directive.
* @param {string} justification The justification of the directive.
*/
constructor(label, value, justification) {
this.label = label;
this.value = value;
this.justification = justification;
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Object to parse ESLint configuration comments.
*/
class ConfigCommentParser {
/**
* Parses a list of "name:string_value" or/and "name" options divided by comma or
* whitespace. Used for "global" comments.
* @param {string} string The string to parse.
* @returns {StringConfig} Result map object of names and string values, or null values if no value was provided.
*/
parseStringConfig(string) {
const items = /** @type {StringConfig} */ ({});
// Collapse whitespace around `:` and `,` to make parsing easier
const trimmedString = string
.trim()
.replace(/(?<!\s)\s*([:,])\s*/gu, "$1");
trimmedString.split(/\s|,+/u).forEach(name => {
if (!name) {
return;
}
// value defaults to null (if not provided), e.g: "foo" => ["foo", null]
const [key, value = null] = name.split(":");
items[key] = value;
});
return items;
}
/**
* Parses a JSON-like config.
* @param {string} string The string to parse.
* @returns {({ok: true, config: RulesConfig}|{ok: false, error: {message: string}})} Result map object
*/
parseJSONLikeConfig(string) {
// Parses a JSON-like comment by the same way as parsing CLI option.
try {
const items =
/** @type {RulesConfig} */ (levn.parse("Object", string)) || {};
/*
* When the configuration has any invalid severities, it should be completely
* ignored. This is because the configuration is not valid and should not be
* applied.
*
* For example, the following configuration is invalid:
*
* "no-alert: 2 no-console: 2"
*
* This results in a configuration of { "no-alert": "2 no-console: 2" }, which is
* not valid. In this case, the configuration should be ignored.
*/
if (isEverySeverityValid(items)) {
return {
ok: true,
config: items,
};
}
} catch {
// levn parsing error: ignore to parse the string by a fallback.
}
/*
* Optionator cannot parse commaless notations.
* But we are supporting that. So this is a fallback for that.
*/
const normalizedString = string
.replace(/(?<![-a-zA-Z0-9/])([-a-zA-Z0-9/]+):/gu, '"$1":')
.replace(/([\]0-9])\s+(?=")/u, "$1,");
try {
const items = JSON.parse(`{${normalizedString}}`);
return {
ok: true,
config: items,
};
} catch (ex) {
const errorMessage = ex instanceof Error ? ex.message : String(ex);
return {
ok: false,
error: {
message: `Failed to parse JSON from '${normalizedString}': ${errorMessage}`,
},
};
}
}
/**
* Parses a config of values separated by comma.
* @param {string} string The string to parse.
* @returns {BooleanConfig} Result map of values and true values
*/
parseListConfig(string) {
const items = /** @type {BooleanConfig} */ ({});
string.split(",").forEach(name => {
const trimmedName = name
.trim()
.replace(
/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/su,
"$<ruleId>",
);
if (trimmedName) {
items[trimmedName] = true;
}
});
return items;
}
/**
* Extract the directive and the justification from a given directive comment and trim them.
* @param {string} value The comment text to extract.
* @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
*/
#extractDirectiveComment(value) {
const match = /\s-{2,}\s/u.exec(value);
if (!match) {
return { directivePart: value.trim(), justificationPart: "" };
}
const directive = value.slice(0, match.index).trim();
const justification = value.slice(match.index + match[0].length).trim();
return { directivePart: directive, justificationPart: justification };
}
/**
* Parses a directive comment into directive text and value.
* @param {string} string The string with the directive to be parsed.
* @returns {DirectiveComment|undefined} The parsed directive or `undefined` if the directive is invalid.
*/
parseDirective(string) {
const { directivePart, justificationPart } =
this.#extractDirectiveComment(string);
const match = directivesPattern.exec(directivePart);
if (!match) {
return undefined;
}
const directiveText = match[1];
const directiveValue = directivePart.slice(
match.index + directiveText.length,
);
return new DirectiveComment(
directiveText,
directiveValue.trim(),
justificationPart,
);
}
}
/**
* @fileoverview A collection of helper classes for implementing `SourceCode`.
* @author Nicholas C. Zakas
*/
/* eslint class-methods-use-this: off -- Required to complete interface. */
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/** @typedef {$eslintcore.VisitTraversalStep} VisitTraversalStep */
/** @typedef {$eslintcore.CallTraversalStep} CallTraversalStep */
/** @typedef {$eslintcore.TraversalStep} TraversalStep */
/** @typedef {$eslintcore.SourceLocation} SourceLocation */
/** @typedef {$eslintcore.SourceLocationWithOffset} SourceLocationWithOffset */
/** @typedef {$eslintcore.SourceRange} SourceRange */
/** @typedef {$eslintcore.Directive} IDirective */
/** @typedef {$eslintcore.DirectiveType} DirectiveType */
/** @typedef {$eslintcore.SourceCodeBaseTypeOptions} SourceCodeBaseTypeOptions */
/**
* @typedef {import("@eslint/core").TextSourceCode<Options>} TextSourceCode
* @template {SourceCodeBaseTypeOptions} [Options=SourceCodeBaseTypeOptions]
*/
/** @typedef {$eslintcore.RuleVisitor} RuleVisitor */
/**
* @typedef {import("./types.ts").CustomRuleVisitorWithExit<RuleVisitorType>} CustomRuleVisitorWithExit
* @template {RuleVisitor} RuleVisitorType
*/
/** @typedef {$typests.CustomRuleTypeDefinitions} CustomRuleTypeDefinitions */
/**
* @typedef {import("./types.ts").CustomRuleDefinitionType<LanguageSpecificOptions, Options>} CustomRuleDefinitionType
* @template {Omit<import("@eslint/core").RuleDefinitionTypeOptions, keyof CustomRuleTypeDefinitions>} LanguageSpecificOptions
* @template {Partial<CustomRuleTypeDefinitions>} Options
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Determines if a node has ESTree-style loc information.
* @param {object} node The node to check.
* @returns {node is {loc:SourceLocation}} `true` if the node has ESTree-style loc information, `false` if not.
*/
function hasESTreeStyleLoc(node) {
return "loc" in node;
}
/**
* Determines if a node has position-style loc information.
* @param {object} node The node to check.
* @returns {node is {position:SourceLocation}} `true` if the node has position-style range information, `false` if not.
*/
function hasPosStyleLoc(node) {
return "position" in node;
}
/**
* Determines if a node has ESTree-style range information.
* @param {object} node The node to check.
* @returns {node is {range:SourceRange}} `true` if the node has ESTree-style range information, `false` if not.
*/
function hasESTreeStyleRange(node) {
return "range" in node;
}
/**
* Determines if a node has position-style range information.
* @param {object} node The node to check.
* @returns {node is {position:SourceLocationWithOffset}} `true` if the node has position-style range information, `false` if not.
*/
function hasPosStyleRange(node) {
return "position" in node;
}
/**
* Performs binary search to find the line number containing a given target index.
* Returns the lower bound - the index of the first element greater than the target.
* **Please note that the `lineStartIndices` should be sorted in ascending order**.
* - Time Complexity: O(log n) - Significantly faster than linear search for large files.
* @param {number[]} lineStartIndices Sorted array of line start indices.
* @param {number} targetIndex The target index to find the line number for.
* @returns {number} The line number for the target index.
*/
function findLineNumberBinarySearch(lineStartIndices, targetIndex) {
let low = 0;
let high = lineStartIndices.length - 1;
while (low < high) {
const mid = ((low + high) / 2) | 0; // Use bitwise OR to floor the division.
if (targetIndex < lineStartIndices[mid]) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* A class to represent a step in the traversal process where a node is visited.
* @implements {VisitTraversalStep}
*/
class VisitNodeStep {
/**
* The type of the step.
* @type {"visit"}
* @readonly
*/
type = "visit";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {1}
* @readonly
*/
kind = 1;
/**
* The target of the step.
* @type {object}
*/
target;
/**
* The phase of the step.
* @type {1|2}
*/
phase;
/**
* The arguments of the step.
* @type {Array<any>}
*/
args;
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {object} 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 }) {
this.target = target;
this.phase = phase;
this.args = args;
}
}
/**
* A class to represent a step in the traversal process where a
* method is called.
* @implements {CallTraversalStep}
*/
class CallMethodStep {
/**
* The type of the step.
* @type {"call"}
* @readonly
*/
type = "call";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {2}
* @readonly
*/
kind = 2;
/**
* The name of the method to call.
* @type {string}
*/
target;
/**
* The arguments to pass to the method.
* @type {Array<any>}
*/
args;
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {string} options.target The target of the step.
* @param {Array<any>} options.args The arguments of the step.
*/
constructor({ target, args }) {
this.target = target;
this.args = args;
}
}
/**
* A class to represent a directive comment.
* @implements {IDirective}
*/
class Directive {
/**
* The type of directive.
* @type {DirectiveType}
* @readonly
*/
type;
/**
* The node representing the directive.
* @type {unknown}
* @readonly
*/
node;
/**
* Everything after the "eslint-disable" portion of the directive,
* but before the "--" that indicates the justification.
* @type {string}
* @readonly
*/
value;
/**
* The justification for the directive.
* @type {string}
* @readonly
*/
justification;
/**
* Creates a new instance.
* @param {Object} options The options for the directive.
* @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive.
* @param {unknown} options.node The node representing the directive.
* @param {string} options.value The value of the directive.
* @param {string} options.justification The justification for the directive.
*/
constructor({ type, node, value, justification }) {
this.type = type;
this.node = node;
this.value = value;
this.justification = justification;
}
}
/**
* Source Code Base Object
* @template {SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}} [Options=SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}]
* @implements {TextSourceCode<Options>}
*/
class TextSourceCodeBase {
/**
* The lines of text in the source code.
* @type {Array<string>}
*/
#lines = [];
/**
* The indices of the start of each line in the source code.
* @type {Array<number>}
*/
#lineStartIndices = [0];
/**
* The pattern to match lineEndings in the source code.
* @type {RegExp}
*/
#lineEndingPattern;
/**
* The AST of the source code.
* @type {Options['RootNode']}
*/
ast;
/**
* The text of the source code.
* @type {string}
*/
text;
/**
* Creates a new instance.
* @param {Object} options The options for the instance.
* @param {string} options.text The source code text.
* @param {Options['RootNode']} options.ast The root AST node.
* @param {RegExp} [options.lineEndingPattern] The pattern to match lineEndings in the source code. Defaults to `/\r?\n/u`.
*/
constructor({ text, ast, lineEndingPattern = /\r?\n/u }) {
this.ast = ast;
this.text = text;
// Remove the global(`g`) and sticky(`y`) flags from the `lineEndingPattern` to avoid issues with lastIndex.
this.#lineEndingPattern = new RegExp(
lineEndingPattern.source,
lineEndingPattern.flags.replace(/[gy]/gu, ""),
);
}
/**
* Finds the next line in the source text and updates `#lines` and `#lineStartIndices`.
* @param {string} text The text to search for the next line.
* @returns {boolean} `true` if a next line was found, `false` otherwise.
*/
#findNextLine(text) {
const match = this.#lineEndingPattern.exec(text);
if (!match) {
return false;
}
this.#lines.push(text.slice(0, match.index));
this.#lineStartIndices.push(
(this.#lineStartIndices.at(-1) ?? 0) +
match.index +
match[0].length,
);
return true;
}
/**
* Ensures `#lines` is lazily calculated from the source text.
* @returns {void}
*/
#ensureLines() {
// If `#lines` has already been calculated, do nothing.
if (this.#lines.length === this.#lineStartIndices.length) {
return;
}
while (
this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
) {
// Continue parsing until no more matches are found.
}
this.#lines.push(this.text.slice(this.#lineStartIndices.at(-1)));
Object.freeze(this.#lines);
}
/**
* Ensures `#lineStartIndices` is lazily calculated up to the specified index.
* @param {number} index The index of a character in a file.
* @returns {void}
*/
#ensureLineStartIndicesFromIndex(index) {
// If we've already parsed up to or beyond this index, do nothing.
if (index <= (this.#lineStartIndices.at(-1) ?? 0)) {
return;
}
while (
index > (this.#lineStartIndices.at(-1) ?? 0) &&
this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
) {
// Continue parsing until no more matches are found.
}
}
/**
* Ensures `#lineStartIndices` is lazily calculated up to the specified loc.
* @param {Object} loc A line/column location.
* @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
* @param {number} lineStart The line number at which the parser starts counting.
* @returns {void}
*/
#ensureLineStartIndicesFromLoc(loc, lineStart) {
// Calculate line indices up to the potentially next line, as it is needed for the followup calculation.
const nextLocLineIndex = loc.line - lineStart + 1;
const lastCalculatedLineIndex = this.#lineStartIndices.length - 1;
let additionalLinesNeeded = nextLocLineIndex - lastCalculatedLineIndex;
// If we've already parsed up to or beyond this line, do nothing.
if (additionalLinesNeeded <= 0) {
return;
}
while (
additionalLinesNeeded > 0 &&
this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
) {
// Continue parsing until no more matches are found or we have enough lines.
additionalLinesNeeded -= 1;
}
}
/**
* Returns the loc information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the loc information for.
* @returns {SourceLocation} The loc information for the node or token.
* @throws {Error} If the node or token does not have loc information.
*/
getLoc(nodeOrToken) {
if (hasESTreeStyleLoc(nodeOrToken)) {
return nodeOrToken.loc;
}
if (hasPosStyleLoc(nodeOrToken)) {
return nodeOrToken.position;
}
throw new Error(
"Custom getLoc() method must be implemented in the subclass.",
);
}
/**
* Converts a source text index into a `{ line: number, column: number }` pair.
* @param {number} index The index of a character in a file.
* @throws {TypeError|RangeError} If non-numeric index or index out of range.
* @returns {{line: number, column: number}} A `{ line: number, column: number }` location object with 0 or 1-indexed line and 0 or 1-indexed column based on language.
* @public
*/
getLocFromIndex(index) {
if (typeof index !== "number") {
throw new TypeError("Expected `index` to be a number.");
}
if (index < 0 || index > this.text.length) {
throw new RangeError(
`Index out of range (requested index ${index}, but source text has length ${this.text.length}).`,
);
}
const {
start: { line: lineStart, column: columnStart },
end: { line: lineEnd, column: columnEnd },
} = this.getLoc(this.ast);
// If the index is at the start, return the start location of the root node.
if (index === 0) {
return {
line: lineStart,
column: columnStart,
};
}
// If the index is `this.text.length`, return the location one "spot" past the last character of the file.
if (index === this.text.length) {
return {
line: lineEnd,
column: columnEnd,
};
}
// Ensure `#lineStartIndices` are lazily calculated.
this.#ensureLineStartIndicesFromIndex(index);
/*
* To figure out which line `index` is on, determine the last place at which index could
* be inserted into `#lineStartIndices` to keep the list sorted.
*/
const lineNumber =
(index >= (this.#lineStartIndices.at(-1) ?? 0)
? this.#lineStartIndices.length
: findLineNumberBinarySearch(this.#lineStartIndices, index)) -
1 +
lineStart;
return {
line: lineNumber,
column:
index -
this.#lineStartIndices[lineNumber - lineStart] +
columnStart,
};
}
/**
* Converts a `{ line: number, column: number }` pair into a source text index.
* @param {Object} loc A line/column location.
* @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
* @param {number} loc.column The column number of the location. (0 or 1-indexed based on language.)
* @throws {TypeError|RangeError} If `loc` is not an object with a numeric
* `line` and `column`, if the `line` is less than or equal to zero or
* the `line` or `column` is out of the expected range.
* @returns {number} The index of the line/column location in a file.
* @public
*/
getIndexFromLoc(loc) {
if (
loc === null ||
typeof loc !== "object" ||
typeof loc.line !== "number" ||
typeof loc.column !== "number"
) {
throw new TypeError(
"Expected `loc` to be an object with numeric `line` and `column` properties.",
);
}
const {
start: { line: lineStart, column: columnStart },
end: { line: lineEnd, column: columnEnd },
} = this.getLoc(this.ast);
if (loc.line < lineStart || lineEnd < loc.line) {
throw new RangeError(
`Line number out of range (line ${loc.line} requested). Valid range: ${lineStart}-${lineEnd}`,
);
}
// If the loc is at the start, return the start index of the root node.
if (loc.line === lineStart && loc.column === columnStart) {
return 0;
}
// If the loc is at the end, return the index one "spot" past the last character of the file.
if (loc.line === lineEnd && loc.column === columnEnd) {
return this.text.length;
}
// Ensure `#lineStartIndices` are lazily calculated.
this.#ensureLineStartIndicesFromLoc(loc, lineStart);
const isLastLine = loc.line === lineEnd;
const lineStartIndex = this.#lineStartIndices[loc.line - lineStart];
const lineEndIndex = isLastLine
? this.text.length
: this.#lineStartIndices[loc.line - lineStart + 1];
const positionIndex = lineStartIndex + loc.column - columnStart;
if (
loc.column < columnStart ||
(isLastLine && positionIndex > lineEndIndex) ||
(!isLastLine && positionIndex >= lineEndIndex)
) {
throw new RangeError(
`Column number out of range (column ${loc.column} requested). Valid range for line ${loc.line}: ${columnStart}-${lineEndIndex - lineStartIndex + columnStart + (isLastLine ? 0 : -1)}`,
);
}
return positionIndex;
}
/**
* Returns the range information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the range information for.
* @returns {SourceRange} The range information for the node or token.
* @throws {Error} If the node or token does not have range information.
*/
getRange(nodeOrToken) {
if (hasESTreeStyleRange(nodeOrToken)) {
return nodeOrToken.range;
}
if (hasPosStyleRange(nodeOrToken)) {
return [
nodeOrToken.position.start.offset,
nodeOrToken.position.end.offset,
];
}
throw new Error(
"Custom getRange() method must be implemented in the subclass.",
);
}
/* eslint-disable no-unused-vars -- Required to complete interface. */
/**
* Returns the parent of the given node.
* @param {Options['SyntaxElementWithLoc']} node The node to get the parent of.
* @returns {Options['SyntaxElementWithLoc']|undefined} The parent of the node.
* @throws {Error} If the method is not implemented in the subclass.
*/
getParent(node) {
throw new Error("Not implemented.");
}
/* eslint-enable no-unused-vars -- Required to complete interface. */
/**
* Gets all the ancestors of a given node
* @param {Options['SyntaxElementWithLoc']} node The node
* @returns {Array<Options['SyntaxElementWithLoc']>} All the ancestor nodes in the AST, not including the provided node, starting
* from the root node at index 0 and going inwards to the parent node.
* @throws {TypeError} When `node` is missing.
*/
getAncestors(node) {
if (!node) {
throw new TypeError("Missing required argument: node.");
}
const ancestorsStartingAtParent = [];
for (
let ancestor = this.getParent(node);
ancestor;
ancestor = this.getParent(ancestor)
) {
ancestorsStartingAtParent.push(ancestor);
}
return ancestorsStartingAtParent.reverse();
}
/**
* Gets the source code for the given node.
* @param {Options['SyntaxElementWithLoc']} [node] The AST node to get the text for.
* @param {number} [beforeCount] The number of characters before the node to retrieve.
* @param {number} [afterCount] The number of characters after the node to retrieve.
* @returns {string} The text representing the AST node.
* @public
*/
getText(node, beforeCount, afterCount) {
if (node) {
const range = this.getRange(node);
return this.text.slice(
Math.max(range[0] - (beforeCount || 0), 0),
range[1] + (afterCount || 0),
);
}
return this.text;
}
/**
* Gets the entire source text split into an array of lines.
* @returns {Array<string>} The source text as an array of lines.
* @public
*/
get lines() {
this.#ensureLines(); // Ensure `#lines` is lazily calculated.
return this.#lines;
}
/**
* Traverse the source code and return the steps that were taken.
* @returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.
*/
traverse() {
throw new Error("Not implemented.");
}
}
exports.CallMethodStep = CallMethodStep;
exports.ConfigCommentParser = ConfigCommentParser;
exports.Directive = Directive;
exports.TextSourceCodeBase = TextSourceCodeBase;
exports.VisitNodeStep = VisitNodeStep;
@@ -0,0 +1,329 @@
export type VisitTraversalStep = $eslintcore.VisitTraversalStep;
export type CallTraversalStep = $eslintcore.CallTraversalStep;
export type TraversalStep = $eslintcore.TraversalStep;
export type SourceLocation = $eslintcore.SourceLocation;
export type SourceLocationWithOffset = $eslintcore.SourceLocationWithOffset;
export type SourceRange = $eslintcore.SourceRange;
export type IDirective = $eslintcore.Directive;
export type DirectiveType = $eslintcore.DirectiveType;
export type SourceCodeBaseTypeOptions = $eslintcore.SourceCodeBaseTypeOptions;
export type TextSourceCode<Options extends SourceCodeBaseTypeOptions = $eslintcore.SourceCodeBaseTypeOptions> = import("@eslint/core").TextSourceCode<Options>;
export type RuleVisitor = $eslintcore.RuleVisitor;
export type CustomRuleVisitorWithExit<RuleVisitorType extends RuleVisitor> = import("./types.cts").CustomRuleVisitorWithExit<RuleVisitorType>;
export type CustomRuleTypeDefinitions = $typests.CustomRuleTypeDefinitions;
export type CustomRuleDefinitionType<LanguageSpecificOptions extends Omit<import("@eslint/core").RuleDefinitionTypeOptions, keyof CustomRuleTypeDefinitions>, Options extends Partial<CustomRuleTypeDefinitions>> = import("./types.cts").CustomRuleDefinitionType<LanguageSpecificOptions, Options>;
export type RuleConfig = $eslintcore.RuleConfig;
export type RulesConfig = $eslintcore.RulesConfig;
export type StringConfig = $typests.StringConfig;
export type BooleanConfig = $typests.BooleanConfig;
/**
* A class to represent a step in the traversal process where a
* method is called.
* @implements {CallTraversalStep}
*/
export class CallMethodStep implements CallTraversalStep {
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {string} options.target The target of the step.
* @param {Array<any>} options.args The arguments of the step.
*/
constructor({ target, args }: {
target: string;
args: Array<any>;
});
/**
* The type of the step.
* @type {"call"}
* @readonly
*/
readonly type: "call";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {2}
* @readonly
*/
readonly kind: 2;
/**
* The name of the method to call.
* @type {string}
*/
target: string;
/**
* The arguments to pass to the method.
* @type {Array<any>}
*/
args: Array<any>;
}
/**
* Object to parse ESLint configuration comments.
*/
export class ConfigCommentParser {
/**
* Parses a list of "name:string_value" or/and "name" options divided by comma or
* whitespace. Used for "global" comments.
* @param {string} string The string to parse.
* @returns {StringConfig} Result map object of names and string values, or null values if no value was provided.
*/
parseStringConfig(string: string): StringConfig;
/**
* Parses a JSON-like config.
* @param {string} string The string to parse.
* @returns {({ok: true, config: RulesConfig}|{ok: false, error: {message: string}})} Result map object
*/
parseJSONLikeConfig(string: string): ({
ok: true;
config: RulesConfig;
} | {
ok: false;
error: {
message: string;
};
});
/**
* Parses a config of values separated by comma.
* @param {string} string The string to parse.
* @returns {BooleanConfig} Result map of values and true values
*/
parseListConfig(string: string): BooleanConfig;
/**
* Parses a directive comment into directive text and value.
* @param {string} string The string with the directive to be parsed.
* @returns {DirectiveComment|undefined} The parsed directive or `undefined` if the directive is invalid.
*/
parseDirective(string: string): DirectiveComment | undefined;
#private;
}
/**
* A class to represent a directive comment.
* @implements {IDirective}
*/
export class Directive implements IDirective {
/**
* Creates a new instance.
* @param {Object} options The options for the directive.
* @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive.
* @param {unknown} options.node The node representing the directive.
* @param {string} options.value The value of the directive.
* @param {string} options.justification The justification for the directive.
*/
constructor({ type, node, value, justification }: {
type: "disable" | "enable" | "disable-next-line" | "disable-line";
node: unknown;
value: string;
justification: string;
});
/**
* The type of directive.
* @type {DirectiveType}
* @readonly
*/
readonly type: DirectiveType;
/**
* The node representing the directive.
* @type {unknown}
* @readonly
*/
readonly node: unknown;
/**
* Everything after the "eslint-disable" portion of the directive,
* but before the "--" that indicates the justification.
* @type {string}
* @readonly
*/
readonly value: string;
/**
* The justification for the directive.
* @type {string}
* @readonly
*/
readonly justification: string;
}
/**
* Source Code Base Object
* @template {SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}} [Options=SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}]
* @implements {TextSourceCode<Options>}
*/
export class TextSourceCodeBase<Options extends SourceCodeBaseTypeOptions & {
RootNode: object;
SyntaxElementWithLoc: object;
} = $eslintcore.SourceCodeBaseTypeOptions & {
RootNode: object;
SyntaxElementWithLoc: object;
}> implements TextSourceCode<Options> {
/**
* Creates a new instance.
* @param {Object} options The options for the instance.
* @param {string} options.text The source code text.
* @param {Options['RootNode']} options.ast The root AST node.
* @param {RegExp} [options.lineEndingPattern] The pattern to match lineEndings in the source code. Defaults to `/\r?\n/u`.
*/
constructor({ text, ast, lineEndingPattern }: {
text: string;
ast: Options["RootNode"];
lineEndingPattern?: RegExp;
});
/**
* The AST of the source code.
* @type {Options['RootNode']}
*/
ast: Options["RootNode"];
/**
* The text of the source code.
* @type {string}
*/
text: string;
/**
* Returns the loc information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the loc information for.
* @returns {SourceLocation} The loc information for the node or token.
* @throws {Error} If the node or token does not have loc information.
*/
getLoc(nodeOrToken: Options["SyntaxElementWithLoc"]): SourceLocation;
/**
* Converts a source text index into a `{ line: number, column: number }` pair.
* @param {number} index The index of a character in a file.
* @throws {TypeError|RangeError} If non-numeric index or index out of range.
* @returns {{line: number, column: number}} A `{ line: number, column: number }` location object with 0 or 1-indexed line and 0 or 1-indexed column based on language.
* @public
*/
public getLocFromIndex(index: number): {
line: number;
column: number;
};
/**
* Converts a `{ line: number, column: number }` pair into a source text index.
* @param {Object} loc A line/column location.
* @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
* @param {number} loc.column The column number of the location. (0 or 1-indexed based on language.)
* @throws {TypeError|RangeError} If `loc` is not an object with a numeric
* `line` and `column`, if the `line` is less than or equal to zero or
* the `line` or `column` is out of the expected range.
* @returns {number} The index of the line/column location in a file.
* @public
*/
public getIndexFromLoc(loc: {
line: number;
column: number;
}): number;
/**
* Returns the range information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the range information for.
* @returns {SourceRange} The range information for the node or token.
* @throws {Error} If the node or token does not have range information.
*/
getRange(nodeOrToken: Options["SyntaxElementWithLoc"]): SourceRange;
/**
* Returns the parent of the given node.
* @param {Options['SyntaxElementWithLoc']} node The node to get the parent of.
* @returns {Options['SyntaxElementWithLoc']|undefined} The parent of the node.
* @throws {Error} If the method is not implemented in the subclass.
*/
getParent(node: Options["SyntaxElementWithLoc"]): Options["SyntaxElementWithLoc"] | undefined;
/**
* Gets all the ancestors of a given node
* @param {Options['SyntaxElementWithLoc']} node The node
* @returns {Array<Options['SyntaxElementWithLoc']>} All the ancestor nodes in the AST, not including the provided node, starting
* from the root node at index 0 and going inwards to the parent node.
* @throws {TypeError} When `node` is missing.
*/
getAncestors(node: Options["SyntaxElementWithLoc"]): Array<Options["SyntaxElementWithLoc"]>;
/**
* Gets the source code for the given node.
* @param {Options['SyntaxElementWithLoc']} [node] The AST node to get the text for.
* @param {number} [beforeCount] The number of characters before the node to retrieve.
* @param {number} [afterCount] The number of characters after the node to retrieve.
* @returns {string} The text representing the AST node.
* @public
*/
public getText(node?: Options["SyntaxElementWithLoc"], beforeCount?: number, afterCount?: number): string;
/**
* Gets the entire source text split into an array of lines.
* @returns {Array<string>} The source text as an array of lines.
* @public
*/
public get lines(): Array<string>;
/**
* Traverse the source code and return the steps that were taken.
* @returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.
*/
traverse(): Iterable<TraversalStep>;
#private;
}
/**
* A class to represent a step in the traversal process where a node is visited.
* @implements {VisitTraversalStep}
*/
export class VisitNodeStep implements VisitTraversalStep {
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {object} 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: object;
phase: 1 | 2;
args: Array<any>;
});
/**
* The type of the step.
* @type {"visit"}
* @readonly
*/
readonly type: "visit";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {1}
* @readonly
*/
readonly kind: 1;
/**
* The target of the step.
* @type {object}
*/
target: object;
/**
* The phase of the step.
* @type {1|2}
*/
phase: 1 | 2;
/**
* The arguments of the step.
* @type {Array<any>}
*/
args: Array<any>;
}
import type * as $eslintcore from "@eslint/core";
import type * as $typests from "./types.cts";
/**
* Represents a directive comment.
*/
declare class DirectiveComment {
/**
* Creates a new directive comment.
* @param {string} label The label of the directive.
* @param {string} value The value of the directive.
* @param {string} justification The justification of the directive.
*/
constructor(label: string, value: string, justification: string);
/**
* The label of the directive, such as "eslint", "eslint-disable", etc.
* @type {string}
*/
label: string;
/**
* The value of the directive (the string after the label).
* @type {string}
*/
value: string;
/**
* The justification of the directive (the string after the --).
* @type {string}
*/
justification: string;
}
export {};
@@ -0,0 +1,81 @@
/**
* @fileoverview Types for the plugin-kit package.
* @author Nicholas C. Zakas
*/
//------------------------------------------------------------------------------
// Imports
//------------------------------------------------------------------------------
import type {
RuleDefinition,
RuleDefinitionTypeOptions,
RuleVisitor,
} from "@eslint/core";
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* Defaults for non-language-related `RuleDefinition` options.
*/
export interface CustomRuleTypeDefinitions {
RuleOptions: unknown[];
MessageIds: string;
ExtRuleDocs: Record<string, unknown>;
}
/**
* A helper type to define language specific specializations of the `RuleDefinition` type.
*
* @example
* ```ts
* type YourRuleDefinition<
* Options extends Partial<CustomRuleTypeDefinitions> = {},
* > = CustomRuleDefinitionType<
* {
* LangOptions: YourLanguageOptions;
* Code: YourSourceCode;
* Visitor: YourRuleVisitor;
* Node: YourNode;
* },
* Options
* >;
* ```
*/
export type CustomRuleDefinitionType<
LanguageSpecificOptions extends Omit<
RuleDefinitionTypeOptions,
keyof CustomRuleTypeDefinitions
>,
Options extends Partial<CustomRuleTypeDefinitions>,
> = RuleDefinition<
// Language specific type options (non-configurable)
LanguageSpecificOptions &
Required<
// Rule specific type options (custom)
Options &
// Rule specific type options (defaults)
Omit<CustomRuleTypeDefinitions, keyof Options>
>
>;
/**
* Adds matching `:exit` selector properties for each key of a `RuleVisitor`.
*/
export type CustomRuleVisitorWithExit<RuleVisitorType extends RuleVisitor> = {
[Key in keyof RuleVisitorType as
| Key
| `${Key & string}:exit`]: RuleVisitorType[Key];
};
/**
* A map of names to string values, or `null` when no value is provided.
*/
export type StringConfig = Record<string, string | null>;
/**
* A map of names to boolean flags.
*/
export type BooleanConfig = Record<string, boolean>;
@@ -0,0 +1,329 @@
export type VisitTraversalStep = $eslintcore.VisitTraversalStep;
export type CallTraversalStep = $eslintcore.CallTraversalStep;
export type TraversalStep = $eslintcore.TraversalStep;
export type SourceLocation = $eslintcore.SourceLocation;
export type SourceLocationWithOffset = $eslintcore.SourceLocationWithOffset;
export type SourceRange = $eslintcore.SourceRange;
export type IDirective = $eslintcore.Directive;
export type DirectiveType = $eslintcore.DirectiveType;
export type SourceCodeBaseTypeOptions = $eslintcore.SourceCodeBaseTypeOptions;
export type TextSourceCode<Options extends SourceCodeBaseTypeOptions = $eslintcore.SourceCodeBaseTypeOptions> = import("@eslint/core").TextSourceCode<Options>;
export type RuleVisitor = $eslintcore.RuleVisitor;
export type CustomRuleVisitorWithExit<RuleVisitorType extends RuleVisitor> = import("./types.ts").CustomRuleVisitorWithExit<RuleVisitorType>;
export type CustomRuleTypeDefinitions = $typests.CustomRuleTypeDefinitions;
export type CustomRuleDefinitionType<LanguageSpecificOptions extends Omit<import("@eslint/core").RuleDefinitionTypeOptions, keyof CustomRuleTypeDefinitions>, Options extends Partial<CustomRuleTypeDefinitions>> = import("./types.ts").CustomRuleDefinitionType<LanguageSpecificOptions, Options>;
export type RuleConfig = $eslintcore.RuleConfig;
export type RulesConfig = $eslintcore.RulesConfig;
export type StringConfig = $typests.StringConfig;
export type BooleanConfig = $typests.BooleanConfig;
/**
* A class to represent a step in the traversal process where a
* method is called.
* @implements {CallTraversalStep}
*/
export class CallMethodStep implements CallTraversalStep {
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {string} options.target The target of the step.
* @param {Array<any>} options.args The arguments of the step.
*/
constructor({ target, args }: {
target: string;
args: Array<any>;
});
/**
* The type of the step.
* @type {"call"}
* @readonly
*/
readonly type: "call";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {2}
* @readonly
*/
readonly kind: 2;
/**
* The name of the method to call.
* @type {string}
*/
target: string;
/**
* The arguments to pass to the method.
* @type {Array<any>}
*/
args: Array<any>;
}
/**
* Object to parse ESLint configuration comments.
*/
export class ConfigCommentParser {
/**
* Parses a list of "name:string_value" or/and "name" options divided by comma or
* whitespace. Used for "global" comments.
* @param {string} string The string to parse.
* @returns {StringConfig} Result map object of names and string values, or null values if no value was provided.
*/
parseStringConfig(string: string): StringConfig;
/**
* Parses a JSON-like config.
* @param {string} string The string to parse.
* @returns {({ok: true, config: RulesConfig}|{ok: false, error: {message: string}})} Result map object
*/
parseJSONLikeConfig(string: string): ({
ok: true;
config: RulesConfig;
} | {
ok: false;
error: {
message: string;
};
});
/**
* Parses a config of values separated by comma.
* @param {string} string The string to parse.
* @returns {BooleanConfig} Result map of values and true values
*/
parseListConfig(string: string): BooleanConfig;
/**
* Parses a directive comment into directive text and value.
* @param {string} string The string with the directive to be parsed.
* @returns {DirectiveComment|undefined} The parsed directive or `undefined` if the directive is invalid.
*/
parseDirective(string: string): DirectiveComment | undefined;
#private;
}
/**
* A class to represent a directive comment.
* @implements {IDirective}
*/
export class Directive implements IDirective {
/**
* Creates a new instance.
* @param {Object} options The options for the directive.
* @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive.
* @param {unknown} options.node The node representing the directive.
* @param {string} options.value The value of the directive.
* @param {string} options.justification The justification for the directive.
*/
constructor({ type, node, value, justification }: {
type: "disable" | "enable" | "disable-next-line" | "disable-line";
node: unknown;
value: string;
justification: string;
});
/**
* The type of directive.
* @type {DirectiveType}
* @readonly
*/
readonly type: DirectiveType;
/**
* The node representing the directive.
* @type {unknown}
* @readonly
*/
readonly node: unknown;
/**
* Everything after the "eslint-disable" portion of the directive,
* but before the "--" that indicates the justification.
* @type {string}
* @readonly
*/
readonly value: string;
/**
* The justification for the directive.
* @type {string}
* @readonly
*/
readonly justification: string;
}
/**
* Source Code Base Object
* @template {SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}} [Options=SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}]
* @implements {TextSourceCode<Options>}
*/
export class TextSourceCodeBase<Options extends SourceCodeBaseTypeOptions & {
RootNode: object;
SyntaxElementWithLoc: object;
} = $eslintcore.SourceCodeBaseTypeOptions & {
RootNode: object;
SyntaxElementWithLoc: object;
}> implements TextSourceCode<Options> {
/**
* Creates a new instance.
* @param {Object} options The options for the instance.
* @param {string} options.text The source code text.
* @param {Options['RootNode']} options.ast The root AST node.
* @param {RegExp} [options.lineEndingPattern] The pattern to match lineEndings in the source code. Defaults to `/\r?\n/u`.
*/
constructor({ text, ast, lineEndingPattern }: {
text: string;
ast: Options["RootNode"];
lineEndingPattern?: RegExp;
});
/**
* The AST of the source code.
* @type {Options['RootNode']}
*/
ast: Options["RootNode"];
/**
* The text of the source code.
* @type {string}
*/
text: string;
/**
* Returns the loc information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the loc information for.
* @returns {SourceLocation} The loc information for the node or token.
* @throws {Error} If the node or token does not have loc information.
*/
getLoc(nodeOrToken: Options["SyntaxElementWithLoc"]): SourceLocation;
/**
* Converts a source text index into a `{ line: number, column: number }` pair.
* @param {number} index The index of a character in a file.
* @throws {TypeError|RangeError} If non-numeric index or index out of range.
* @returns {{line: number, column: number}} A `{ line: number, column: number }` location object with 0 or 1-indexed line and 0 or 1-indexed column based on language.
* @public
*/
public getLocFromIndex(index: number): {
line: number;
column: number;
};
/**
* Converts a `{ line: number, column: number }` pair into a source text index.
* @param {Object} loc A line/column location.
* @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
* @param {number} loc.column The column number of the location. (0 or 1-indexed based on language.)
* @throws {TypeError|RangeError} If `loc` is not an object with a numeric
* `line` and `column`, if the `line` is less than or equal to zero or
* the `line` or `column` is out of the expected range.
* @returns {number} The index of the line/column location in a file.
* @public
*/
public getIndexFromLoc(loc: {
line: number;
column: number;
}): number;
/**
* Returns the range information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the range information for.
* @returns {SourceRange} The range information for the node or token.
* @throws {Error} If the node or token does not have range information.
*/
getRange(nodeOrToken: Options["SyntaxElementWithLoc"]): SourceRange;
/**
* Returns the parent of the given node.
* @param {Options['SyntaxElementWithLoc']} node The node to get the parent of.
* @returns {Options['SyntaxElementWithLoc']|undefined} The parent of the node.
* @throws {Error} If the method is not implemented in the subclass.
*/
getParent(node: Options["SyntaxElementWithLoc"]): Options["SyntaxElementWithLoc"] | undefined;
/**
* Gets all the ancestors of a given node
* @param {Options['SyntaxElementWithLoc']} node The node
* @returns {Array<Options['SyntaxElementWithLoc']>} All the ancestor nodes in the AST, not including the provided node, starting
* from the root node at index 0 and going inwards to the parent node.
* @throws {TypeError} When `node` is missing.
*/
getAncestors(node: Options["SyntaxElementWithLoc"]): Array<Options["SyntaxElementWithLoc"]>;
/**
* Gets the source code for the given node.
* @param {Options['SyntaxElementWithLoc']} [node] The AST node to get the text for.
* @param {number} [beforeCount] The number of characters before the node to retrieve.
* @param {number} [afterCount] The number of characters after the node to retrieve.
* @returns {string} The text representing the AST node.
* @public
*/
public getText(node?: Options["SyntaxElementWithLoc"], beforeCount?: number, afterCount?: number): string;
/**
* Gets the entire source text split into an array of lines.
* @returns {Array<string>} The source text as an array of lines.
* @public
*/
public get lines(): Array<string>;
/**
* Traverse the source code and return the steps that were taken.
* @returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.
*/
traverse(): Iterable<TraversalStep>;
#private;
}
/**
* A class to represent a step in the traversal process where a node is visited.
* @implements {VisitTraversalStep}
*/
export class VisitNodeStep implements VisitTraversalStep {
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {object} 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: object;
phase: 1 | 2;
args: Array<any>;
});
/**
* The type of the step.
* @type {"visit"}
* @readonly
*/
readonly type: "visit";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {1}
* @readonly
*/
readonly kind: 1;
/**
* The target of the step.
* @type {object}
*/
target: object;
/**
* The phase of the step.
* @type {1|2}
*/
phase: 1 | 2;
/**
* The arguments of the step.
* @type {Array<any>}
*/
args: Array<any>;
}
import type * as $eslintcore from "@eslint/core";
import type * as $typests from "./types.ts";
/**
* Represents a directive comment.
*/
declare class DirectiveComment {
/**
* Creates a new directive comment.
* @param {string} label The label of the directive.
* @param {string} value The value of the directive.
* @param {string} justification The justification of the directive.
*/
constructor(label: string, value: string, justification: string);
/**
* The label of the directive, such as "eslint", "eslint-disable", etc.
* @type {string}
*/
label: string;
/**
* The value of the directive (the string after the label).
* @type {string}
*/
value: string;
/**
* The justification of the directive (the string after the --).
* @type {string}
*/
justification: string;
}
export {};
@@ -0,0 +1,890 @@
// @ts-self-types="./index.d.ts"
import levn from 'levn';
/**
* @fileoverview Config Comment Parser
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/** @import * as $eslintcore from "@eslint/core"; */
/** @typedef {$eslintcore.RuleConfig} RuleConfig */
/** @typedef {$eslintcore.RulesConfig} RulesConfig */
/** @import * as $typests from "./types.ts"; */
/** @typedef {$typests.StringConfig} StringConfig */
/** @typedef {$typests.BooleanConfig} BooleanConfig */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const directivesPattern = /^([a-z]+(?:-[a-z]+)*)(?:\s|$)/u;
const validSeverities = new Set([0, 1, 2, "off", "warn", "error"]);
/**
* Determines if the severity in the rule configuration is valid.
* @param {RuleConfig} ruleConfig A rule's configuration.
* @returns {boolean} `true` if the severity is valid, otherwise `false`.
*/
function isSeverityValid(ruleConfig) {
const severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
return validSeverities.has(severity);
}
/**
* Determines if all severities in the rules configuration are valid.
* @param {RulesConfig} rulesConfig The rules configuration to check.
* @returns {boolean} `true` if all severities are valid, otherwise `false`.
*/
function isEverySeverityValid(rulesConfig) {
return Object.values(rulesConfig).every(isSeverityValid);
}
/**
* Represents a directive comment.
*/
class DirectiveComment {
/**
* The label of the directive, such as "eslint", "eslint-disable", etc.
* @type {string}
*/
label = "";
/**
* The value of the directive (the string after the label).
* @type {string}
*/
value = "";
/**
* The justification of the directive (the string after the --).
* @type {string}
*/
justification = "";
/**
* Creates a new directive comment.
* @param {string} label The label of the directive.
* @param {string} value The value of the directive.
* @param {string} justification The justification of the directive.
*/
constructor(label, value, justification) {
this.label = label;
this.value = value;
this.justification = justification;
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
/**
* Object to parse ESLint configuration comments.
*/
class ConfigCommentParser {
/**
* Parses a list of "name:string_value" or/and "name" options divided by comma or
* whitespace. Used for "global" comments.
* @param {string} string The string to parse.
* @returns {StringConfig} Result map object of names and string values, or null values if no value was provided.
*/
parseStringConfig(string) {
const items = /** @type {StringConfig} */ ({});
// Collapse whitespace around `:` and `,` to make parsing easier
const trimmedString = string
.trim()
.replace(/(?<!\s)\s*([:,])\s*/gu, "$1");
trimmedString.split(/\s|,+/u).forEach(name => {
if (!name) {
return;
}
// value defaults to null (if not provided), e.g: "foo" => ["foo", null]
const [key, value = null] = name.split(":");
items[key] = value;
});
return items;
}
/**
* Parses a JSON-like config.
* @param {string} string The string to parse.
* @returns {({ok: true, config: RulesConfig}|{ok: false, error: {message: string}})} Result map object
*/
parseJSONLikeConfig(string) {
// Parses a JSON-like comment by the same way as parsing CLI option.
try {
const items =
/** @type {RulesConfig} */ (levn.parse("Object", string)) || {};
/*
* When the configuration has any invalid severities, it should be completely
* ignored. This is because the configuration is not valid and should not be
* applied.
*
* For example, the following configuration is invalid:
*
* "no-alert: 2 no-console: 2"
*
* This results in a configuration of { "no-alert": "2 no-console: 2" }, which is
* not valid. In this case, the configuration should be ignored.
*/
if (isEverySeverityValid(items)) {
return {
ok: true,
config: items,
};
}
} catch {
// levn parsing error: ignore to parse the string by a fallback.
}
/*
* Optionator cannot parse commaless notations.
* But we are supporting that. So this is a fallback for that.
*/
const normalizedString = string
.replace(/(?<![-a-zA-Z0-9/])([-a-zA-Z0-9/]+):/gu, '"$1":')
.replace(/([\]0-9])\s+(?=")/u, "$1,");
try {
const items = JSON.parse(`{${normalizedString}}`);
return {
ok: true,
config: items,
};
} catch (ex) {
const errorMessage = ex instanceof Error ? ex.message : String(ex);
return {
ok: false,
error: {
message: `Failed to parse JSON from '${normalizedString}': ${errorMessage}`,
},
};
}
}
/**
* Parses a config of values separated by comma.
* @param {string} string The string to parse.
* @returns {BooleanConfig} Result map of values and true values
*/
parseListConfig(string) {
const items = /** @type {BooleanConfig} */ ({});
string.split(",").forEach(name => {
const trimmedName = name
.trim()
.replace(
/^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/su,
"$<ruleId>",
);
if (trimmedName) {
items[trimmedName] = true;
}
});
return items;
}
/**
* Extract the directive and the justification from a given directive comment and trim them.
* @param {string} value The comment text to extract.
* @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
*/
#extractDirectiveComment(value) {
const match = /\s-{2,}\s/u.exec(value);
if (!match) {
return { directivePart: value.trim(), justificationPart: "" };
}
const directive = value.slice(0, match.index).trim();
const justification = value.slice(match.index + match[0].length).trim();
return { directivePart: directive, justificationPart: justification };
}
/**
* Parses a directive comment into directive text and value.
* @param {string} string The string with the directive to be parsed.
* @returns {DirectiveComment|undefined} The parsed directive or `undefined` if the directive is invalid.
*/
parseDirective(string) {
const { directivePart, justificationPart } =
this.#extractDirectiveComment(string);
const match = directivesPattern.exec(directivePart);
if (!match) {
return undefined;
}
const directiveText = match[1];
const directiveValue = directivePart.slice(
match.index + directiveText.length,
);
return new DirectiveComment(
directiveText,
directiveValue.trim(),
justificationPart,
);
}
}
/**
* @fileoverview A collection of helper classes for implementing `SourceCode`.
* @author Nicholas C. Zakas
*/
/* eslint class-methods-use-this: off -- Required to complete interface. */
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/** @typedef {$eslintcore.VisitTraversalStep} VisitTraversalStep */
/** @typedef {$eslintcore.CallTraversalStep} CallTraversalStep */
/** @typedef {$eslintcore.TraversalStep} TraversalStep */
/** @typedef {$eslintcore.SourceLocation} SourceLocation */
/** @typedef {$eslintcore.SourceLocationWithOffset} SourceLocationWithOffset */
/** @typedef {$eslintcore.SourceRange} SourceRange */
/** @typedef {$eslintcore.Directive} IDirective */
/** @typedef {$eslintcore.DirectiveType} DirectiveType */
/** @typedef {$eslintcore.SourceCodeBaseTypeOptions} SourceCodeBaseTypeOptions */
/**
* @typedef {import("@eslint/core").TextSourceCode<Options>} TextSourceCode
* @template {SourceCodeBaseTypeOptions} [Options=SourceCodeBaseTypeOptions]
*/
/** @typedef {$eslintcore.RuleVisitor} RuleVisitor */
/**
* @typedef {import("./types.ts").CustomRuleVisitorWithExit<RuleVisitorType>} CustomRuleVisitorWithExit
* @template {RuleVisitor} RuleVisitorType
*/
/** @typedef {$typests.CustomRuleTypeDefinitions} CustomRuleTypeDefinitions */
/**
* @typedef {import("./types.ts").CustomRuleDefinitionType<LanguageSpecificOptions, Options>} CustomRuleDefinitionType
* @template {Omit<import("@eslint/core").RuleDefinitionTypeOptions, keyof CustomRuleTypeDefinitions>} LanguageSpecificOptions
* @template {Partial<CustomRuleTypeDefinitions>} Options
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Determines if a node has ESTree-style loc information.
* @param {object} node The node to check.
* @returns {node is {loc:SourceLocation}} `true` if the node has ESTree-style loc information, `false` if not.
*/
function hasESTreeStyleLoc(node) {
return "loc" in node;
}
/**
* Determines if a node has position-style loc information.
* @param {object} node The node to check.
* @returns {node is {position:SourceLocation}} `true` if the node has position-style range information, `false` if not.
*/
function hasPosStyleLoc(node) {
return "position" in node;
}
/**
* Determines if a node has ESTree-style range information.
* @param {object} node The node to check.
* @returns {node is {range:SourceRange}} `true` if the node has ESTree-style range information, `false` if not.
*/
function hasESTreeStyleRange(node) {
return "range" in node;
}
/**
* Determines if a node has position-style range information.
* @param {object} node The node to check.
* @returns {node is {position:SourceLocationWithOffset}} `true` if the node has position-style range information, `false` if not.
*/
function hasPosStyleRange(node) {
return "position" in node;
}
/**
* Performs binary search to find the line number containing a given target index.
* Returns the lower bound - the index of the first element greater than the target.
* **Please note that the `lineStartIndices` should be sorted in ascending order**.
* - Time Complexity: O(log n) - Significantly faster than linear search for large files.
* @param {number[]} lineStartIndices Sorted array of line start indices.
* @param {number} targetIndex The target index to find the line number for.
* @returns {number} The line number for the target index.
*/
function findLineNumberBinarySearch(lineStartIndices, targetIndex) {
let low = 0;
let high = lineStartIndices.length - 1;
while (low < high) {
const mid = ((low + high) / 2) | 0; // Use bitwise OR to floor the division.
if (targetIndex < lineStartIndices[mid]) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* A class to represent a step in the traversal process where a node is visited.
* @implements {VisitTraversalStep}
*/
class VisitNodeStep {
/**
* The type of the step.
* @type {"visit"}
* @readonly
*/
type = "visit";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {1}
* @readonly
*/
kind = 1;
/**
* The target of the step.
* @type {object}
*/
target;
/**
* The phase of the step.
* @type {1|2}
*/
phase;
/**
* The arguments of the step.
* @type {Array<any>}
*/
args;
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {object} 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 }) {
this.target = target;
this.phase = phase;
this.args = args;
}
}
/**
* A class to represent a step in the traversal process where a
* method is called.
* @implements {CallTraversalStep}
*/
class CallMethodStep {
/**
* The type of the step.
* @type {"call"}
* @readonly
*/
type = "call";
/**
* The kind of the step. Represents the same data as the `type` property
* but it's a number for performance.
* @type {2}
* @readonly
*/
kind = 2;
/**
* The name of the method to call.
* @type {string}
*/
target;
/**
* The arguments to pass to the method.
* @type {Array<any>}
*/
args;
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {string} options.target The target of the step.
* @param {Array<any>} options.args The arguments of the step.
*/
constructor({ target, args }) {
this.target = target;
this.args = args;
}
}
/**
* A class to represent a directive comment.
* @implements {IDirective}
*/
class Directive {
/**
* The type of directive.
* @type {DirectiveType}
* @readonly
*/
type;
/**
* The node representing the directive.
* @type {unknown}
* @readonly
*/
node;
/**
* Everything after the "eslint-disable" portion of the directive,
* but before the "--" that indicates the justification.
* @type {string}
* @readonly
*/
value;
/**
* The justification for the directive.
* @type {string}
* @readonly
*/
justification;
/**
* Creates a new instance.
* @param {Object} options The options for the directive.
* @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive.
* @param {unknown} options.node The node representing the directive.
* @param {string} options.value The value of the directive.
* @param {string} options.justification The justification for the directive.
*/
constructor({ type, node, value, justification }) {
this.type = type;
this.node = node;
this.value = value;
this.justification = justification;
}
}
/**
* Source Code Base Object
* @template {SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}} [Options=SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}]
* @implements {TextSourceCode<Options>}
*/
class TextSourceCodeBase {
/**
* The lines of text in the source code.
* @type {Array<string>}
*/
#lines = [];
/**
* The indices of the start of each line in the source code.
* @type {Array<number>}
*/
#lineStartIndices = [0];
/**
* The pattern to match lineEndings in the source code.
* @type {RegExp}
*/
#lineEndingPattern;
/**
* The AST of the source code.
* @type {Options['RootNode']}
*/
ast;
/**
* The text of the source code.
* @type {string}
*/
text;
/**
* Creates a new instance.
* @param {Object} options The options for the instance.
* @param {string} options.text The source code text.
* @param {Options['RootNode']} options.ast The root AST node.
* @param {RegExp} [options.lineEndingPattern] The pattern to match lineEndings in the source code. Defaults to `/\r?\n/u`.
*/
constructor({ text, ast, lineEndingPattern = /\r?\n/u }) {
this.ast = ast;
this.text = text;
// Remove the global(`g`) and sticky(`y`) flags from the `lineEndingPattern` to avoid issues with lastIndex.
this.#lineEndingPattern = new RegExp(
lineEndingPattern.source,
lineEndingPattern.flags.replace(/[gy]/gu, ""),
);
}
/**
* Finds the next line in the source text and updates `#lines` and `#lineStartIndices`.
* @param {string} text The text to search for the next line.
* @returns {boolean} `true` if a next line was found, `false` otherwise.
*/
#findNextLine(text) {
const match = this.#lineEndingPattern.exec(text);
if (!match) {
return false;
}
this.#lines.push(text.slice(0, match.index));
this.#lineStartIndices.push(
(this.#lineStartIndices.at(-1) ?? 0) +
match.index +
match[0].length,
);
return true;
}
/**
* Ensures `#lines` is lazily calculated from the source text.
* @returns {void}
*/
#ensureLines() {
// If `#lines` has already been calculated, do nothing.
if (this.#lines.length === this.#lineStartIndices.length) {
return;
}
while (
this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
) {
// Continue parsing until no more matches are found.
}
this.#lines.push(this.text.slice(this.#lineStartIndices.at(-1)));
Object.freeze(this.#lines);
}
/**
* Ensures `#lineStartIndices` is lazily calculated up to the specified index.
* @param {number} index The index of a character in a file.
* @returns {void}
*/
#ensureLineStartIndicesFromIndex(index) {
// If we've already parsed up to or beyond this index, do nothing.
if (index <= (this.#lineStartIndices.at(-1) ?? 0)) {
return;
}
while (
index > (this.#lineStartIndices.at(-1) ?? 0) &&
this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
) {
// Continue parsing until no more matches are found.
}
}
/**
* Ensures `#lineStartIndices` is lazily calculated up to the specified loc.
* @param {Object} loc A line/column location.
* @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
* @param {number} lineStart The line number at which the parser starts counting.
* @returns {void}
*/
#ensureLineStartIndicesFromLoc(loc, lineStart) {
// Calculate line indices up to the potentially next line, as it is needed for the followup calculation.
const nextLocLineIndex = loc.line - lineStart + 1;
const lastCalculatedLineIndex = this.#lineStartIndices.length - 1;
let additionalLinesNeeded = nextLocLineIndex - lastCalculatedLineIndex;
// If we've already parsed up to or beyond this line, do nothing.
if (additionalLinesNeeded <= 0) {
return;
}
while (
additionalLinesNeeded > 0 &&
this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
) {
// Continue parsing until no more matches are found or we have enough lines.
additionalLinesNeeded -= 1;
}
}
/**
* Returns the loc information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the loc information for.
* @returns {SourceLocation} The loc information for the node or token.
* @throws {Error} If the node or token does not have loc information.
*/
getLoc(nodeOrToken) {
if (hasESTreeStyleLoc(nodeOrToken)) {
return nodeOrToken.loc;
}
if (hasPosStyleLoc(nodeOrToken)) {
return nodeOrToken.position;
}
throw new Error(
"Custom getLoc() method must be implemented in the subclass.",
);
}
/**
* Converts a source text index into a `{ line: number, column: number }` pair.
* @param {number} index The index of a character in a file.
* @throws {TypeError|RangeError} If non-numeric index or index out of range.
* @returns {{line: number, column: number}} A `{ line: number, column: number }` location object with 0 or 1-indexed line and 0 or 1-indexed column based on language.
* @public
*/
getLocFromIndex(index) {
if (typeof index !== "number") {
throw new TypeError("Expected `index` to be a number.");
}
if (index < 0 || index > this.text.length) {
throw new RangeError(
`Index out of range (requested index ${index}, but source text has length ${this.text.length}).`,
);
}
const {
start: { line: lineStart, column: columnStart },
end: { line: lineEnd, column: columnEnd },
} = this.getLoc(this.ast);
// If the index is at the start, return the start location of the root node.
if (index === 0) {
return {
line: lineStart,
column: columnStart,
};
}
// If the index is `this.text.length`, return the location one "spot" past the last character of the file.
if (index === this.text.length) {
return {
line: lineEnd,
column: columnEnd,
};
}
// Ensure `#lineStartIndices` are lazily calculated.
this.#ensureLineStartIndicesFromIndex(index);
/*
* To figure out which line `index` is on, determine the last place at which index could
* be inserted into `#lineStartIndices` to keep the list sorted.
*/
const lineNumber =
(index >= (this.#lineStartIndices.at(-1) ?? 0)
? this.#lineStartIndices.length
: findLineNumberBinarySearch(this.#lineStartIndices, index)) -
1 +
lineStart;
return {
line: lineNumber,
column:
index -
this.#lineStartIndices[lineNumber - lineStart] +
columnStart,
};
}
/**
* Converts a `{ line: number, column: number }` pair into a source text index.
* @param {Object} loc A line/column location.
* @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
* @param {number} loc.column The column number of the location. (0 or 1-indexed based on language.)
* @throws {TypeError|RangeError} If `loc` is not an object with a numeric
* `line` and `column`, if the `line` is less than or equal to zero or
* the `line` or `column` is out of the expected range.
* @returns {number} The index of the line/column location in a file.
* @public
*/
getIndexFromLoc(loc) {
if (
loc === null ||
typeof loc !== "object" ||
typeof loc.line !== "number" ||
typeof loc.column !== "number"
) {
throw new TypeError(
"Expected `loc` to be an object with numeric `line` and `column` properties.",
);
}
const {
start: { line: lineStart, column: columnStart },
end: { line: lineEnd, column: columnEnd },
} = this.getLoc(this.ast);
if (loc.line < lineStart || lineEnd < loc.line) {
throw new RangeError(
`Line number out of range (line ${loc.line} requested). Valid range: ${lineStart}-${lineEnd}`,
);
}
// If the loc is at the start, return the start index of the root node.
if (loc.line === lineStart && loc.column === columnStart) {
return 0;
}
// If the loc is at the end, return the index one "spot" past the last character of the file.
if (loc.line === lineEnd && loc.column === columnEnd) {
return this.text.length;
}
// Ensure `#lineStartIndices` are lazily calculated.
this.#ensureLineStartIndicesFromLoc(loc, lineStart);
const isLastLine = loc.line === lineEnd;
const lineStartIndex = this.#lineStartIndices[loc.line - lineStart];
const lineEndIndex = isLastLine
? this.text.length
: this.#lineStartIndices[loc.line - lineStart + 1];
const positionIndex = lineStartIndex + loc.column - columnStart;
if (
loc.column < columnStart ||
(isLastLine && positionIndex > lineEndIndex) ||
(!isLastLine && positionIndex >= lineEndIndex)
) {
throw new RangeError(
`Column number out of range (column ${loc.column} requested). Valid range for line ${loc.line}: ${columnStart}-${lineEndIndex - lineStartIndex + columnStart + (isLastLine ? 0 : -1)}`,
);
}
return positionIndex;
}
/**
* Returns the range information for the given node or token.
* @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the range information for.
* @returns {SourceRange} The range information for the node or token.
* @throws {Error} If the node or token does not have range information.
*/
getRange(nodeOrToken) {
if (hasESTreeStyleRange(nodeOrToken)) {
return nodeOrToken.range;
}
if (hasPosStyleRange(nodeOrToken)) {
return [
nodeOrToken.position.start.offset,
nodeOrToken.position.end.offset,
];
}
throw new Error(
"Custom getRange() method must be implemented in the subclass.",
);
}
/* eslint-disable no-unused-vars -- Required to complete interface. */
/**
* Returns the parent of the given node.
* @param {Options['SyntaxElementWithLoc']} node The node to get the parent of.
* @returns {Options['SyntaxElementWithLoc']|undefined} The parent of the node.
* @throws {Error} If the method is not implemented in the subclass.
*/
getParent(node) {
throw new Error("Not implemented.");
}
/* eslint-enable no-unused-vars -- Required to complete interface. */
/**
* Gets all the ancestors of a given node
* @param {Options['SyntaxElementWithLoc']} node The node
* @returns {Array<Options['SyntaxElementWithLoc']>} All the ancestor nodes in the AST, not including the provided node, starting
* from the root node at index 0 and going inwards to the parent node.
* @throws {TypeError} When `node` is missing.
*/
getAncestors(node) {
if (!node) {
throw new TypeError("Missing required argument: node.");
}
const ancestorsStartingAtParent = [];
for (
let ancestor = this.getParent(node);
ancestor;
ancestor = this.getParent(ancestor)
) {
ancestorsStartingAtParent.push(ancestor);
}
return ancestorsStartingAtParent.reverse();
}
/**
* Gets the source code for the given node.
* @param {Options['SyntaxElementWithLoc']} [node] The AST node to get the text for.
* @param {number} [beforeCount] The number of characters before the node to retrieve.
* @param {number} [afterCount] The number of characters after the node to retrieve.
* @returns {string} The text representing the AST node.
* @public
*/
getText(node, beforeCount, afterCount) {
if (node) {
const range = this.getRange(node);
return this.text.slice(
Math.max(range[0] - (beforeCount || 0), 0),
range[1] + (afterCount || 0),
);
}
return this.text;
}
/**
* Gets the entire source text split into an array of lines.
* @returns {Array<string>} The source text as an array of lines.
* @public
*/
get lines() {
this.#ensureLines(); // Ensure `#lines` is lazily calculated.
return this.#lines;
}
/**
* Traverse the source code and return the steps that were taken.
* @returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.
*/
traverse() {
throw new Error("Not implemented.");
}
}
export { CallMethodStep, ConfigCommentParser, Directive, TextSourceCodeBase, VisitNodeStep };
@@ -0,0 +1,46 @@
/**
* @fileoverview Types for the plugin-kit package.
* @author Nicholas C. Zakas
*/
import type { RuleDefinition, RuleDefinitionTypeOptions, RuleVisitor } from "@eslint/core";
/**
* Defaults for non-language-related `RuleDefinition` options.
*/
export interface CustomRuleTypeDefinitions {
RuleOptions: unknown[];
MessageIds: string;
ExtRuleDocs: Record<string, unknown>;
}
/**
* A helper type to define language specific specializations of the `RuleDefinition` type.
*
* @example
* ```ts
* type YourRuleDefinition<
* Options extends Partial<CustomRuleTypeDefinitions> = {},
* > = CustomRuleDefinitionType<
* {
* LangOptions: YourLanguageOptions;
* Code: YourSourceCode;
* Visitor: YourRuleVisitor;
* Node: YourNode;
* },
* Options
* >;
* ```
*/
export type CustomRuleDefinitionType<LanguageSpecificOptions extends Omit<RuleDefinitionTypeOptions, keyof CustomRuleTypeDefinitions>, Options extends Partial<CustomRuleTypeDefinitions>> = RuleDefinition<LanguageSpecificOptions & Required<Options & Omit<CustomRuleTypeDefinitions, keyof Options>>>;
/**
* Adds matching `:exit` selector properties for each key of a `RuleVisitor`.
*/
export type CustomRuleVisitorWithExit<RuleVisitorType extends RuleVisitor> = {
[Key in keyof RuleVisitorType as Key | `${Key & string}:exit`]: RuleVisitorType[Key];
};
/**
* A map of names to string values, or `null` when no value is provided.
*/
export type StringConfig = Record<string, string | null>;
/**
* A map of names to boolean flags.
*/
export type BooleanConfig = Record<string, boolean>;
@@ -0,0 +1,81 @@
/**
* @fileoverview Types for the plugin-kit package.
* @author Nicholas C. Zakas
*/
//------------------------------------------------------------------------------
// Imports
//------------------------------------------------------------------------------
import type {
RuleDefinition,
RuleDefinitionTypeOptions,
RuleVisitor,
} from "@eslint/core";
//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------
/**
* Defaults for non-language-related `RuleDefinition` options.
*/
export interface CustomRuleTypeDefinitions {
RuleOptions: unknown[];
MessageIds: string;
ExtRuleDocs: Record<string, unknown>;
}
/**
* A helper type to define language specific specializations of the `RuleDefinition` type.
*
* @example
* ```ts
* type YourRuleDefinition<
* Options extends Partial<CustomRuleTypeDefinitions> = {},
* > = CustomRuleDefinitionType<
* {
* LangOptions: YourLanguageOptions;
* Code: YourSourceCode;
* Visitor: YourRuleVisitor;
* Node: YourNode;
* },
* Options
* >;
* ```
*/
export type CustomRuleDefinitionType<
LanguageSpecificOptions extends Omit<
RuleDefinitionTypeOptions,
keyof CustomRuleTypeDefinitions
>,
Options extends Partial<CustomRuleTypeDefinitions>,
> = RuleDefinition<
// Language specific type options (non-configurable)
LanguageSpecificOptions &
Required<
// Rule specific type options (custom)
Options &
// Rule specific type options (defaults)
Omit<CustomRuleTypeDefinitions, keyof Options>
>
>;
/**
* Adds matching `:exit` selector properties for each key of a `RuleVisitor`.
*/
export type CustomRuleVisitorWithExit<RuleVisitorType extends RuleVisitor> = {
[Key in keyof RuleVisitorType as
| Key
| `${Key & string}:exit`]: RuleVisitorType[Key];
};
/**
* A map of names to string values, or `null` when no value is provided.
*/
export type StringConfig = Record<string, string | null>;
/**
* A map of names to boolean flags.
*/
export type BooleanConfig = Record<string, boolean>;
@@ -0,0 +1,63 @@
{
"name": "@eslint/plugin-kit",
"version": "0.6.1",
"description": "Utilities for building ESLint plugins.",
"author": "Nicholas C. Zakas",
"type": "module",
"main": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"exports": {
"require": {
"types": "./dist/cjs/index.d.cts",
"default": "./dist/cjs/index.cjs"
},
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/eslint/rewrite.git",
"directory": "packages/plugin-kit"
},
"bugs": {
"url": "https://github.com/eslint/rewrite/issues"
},
"homepage": "https://github.com/eslint/rewrite/tree/main/packages/plugin-kit#readme",
"scripts": {
"build:dedupe-types": "node ../../tools/dedupe-types.js dist/cjs/index.cjs dist/esm/index.js",
"build:cts": "node ../../tools/build-cts.js dist/esm/index.d.ts dist/cjs/index.d.cts",
"build": "rollup -c && npm run build:dedupe-types && tsc -p tsconfig.esm.json && npm run build:cts",
"lint:types": "attw --pack",
"pretest": "npm run build",
"test": "npm run test:types && npm run test:unit",
"test:coverage": "npm run build && c8 npm run test:unit",
"test:jsr": "npx -y jsr@latest publish --dry-run",
"test:types": "tsc -p tests/types/tsconfig.json",
"test:unit": "mocha \"tests/**/*.test.js\""
},
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin"
],
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^1.1.1",
"levn": "^0.4.1"
},
"devDependencies": {
"@types/levn": "^0.4.0",
"rollup-plugin-copy": "^3.5.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
}
+138
View File
@@ -0,0 +1,138 @@
{
"name": "eslint-plugin-jsonc",
"version": "3.1.2",
"description": "ESLint plugin for JSON, JSONC and JSON5 files.",
"type": "module",
"main": "./dist/index.mjs",
"exports": {
".": {
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs",
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"files": [
"dist"
],
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"scripts": {
"prebuild": "npm run -s clean",
"build": "npm run build:ts",
"build:ts": "tsdown",
"clean": "node -e \"import('fs').then(fs=>['dist','coverage'].forEach(d=>{try{fs.rmSync(d,{recursive:true,force:true})}catch(e){}}))\"",
"lint": "npm run eslint && npm run tsc",
"tsc": "tsc --noEmit",
"eslint": "eslint . --config=./eslint.config.mjs --no-config-lookup",
"eslint-fix": "npm run eslint -- --fix",
"test:base": "env-cmd -e test -- mocha --import=tsx/esm \"tests/lib/**/*.ts\" --reporter dot --timeout 60000",
"test": "npm run test:base",
"test:c8": "c8 --reporter=lcov npm run test:base",
"pretest:integrations": "npm run build:ts && npm pack",
"test:integrations": "mocha --import=tsx/esm \"tests-integrations/lib/**/*.ts\" --reporter dot --timeout 120000",
"update": "node --import=tsx/esm ./tools/update.ts && npm run eslint-fix && npm run test:c8",
"update-only": "node --import=tsx/esm ./tools/update.ts",
"new": "node --import=tsx/esm ./tools/new-rule.ts",
"predocs:watch": "npm run build:ts",
"docs:watch": "vitepress dev docs",
"docs:build": "npm run build:ts && vitepress build docs",
"prerelease": "npm run test && npm run build",
"release": "changeset publish",
"preversion": "npm test && git add .",
"version": "env-cmd -e version -- npm run update && git add .",
"preversion:ci": "npm run build",
"version:ci": "env-cmd -e version-ci -- npm run update && changeset version"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ota-meshi/eslint-plugin-jsonc.git"
},
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin",
"lint",
"jsonc",
"json5",
"json"
],
"author": "Yosuke Ota",
"funding": "https://github.com/sponsors/ota-meshi",
"license": "MIT",
"bugs": {
"url": "https://github.com/ota-meshi/eslint-plugin-jsonc/issues"
},
"homepage": "https://ota-meshi.github.io/eslint-plugin-jsonc/",
"dependencies": {
"@eslint-community/eslint-utils": "^4.5.1",
"@eslint/core": "^1.0.1",
"@eslint/plugin-kit": "^0.6.0",
"@ota-meshi/ast-token-store": "^0.3.0",
"diff-sequences": "^29.6.3",
"eslint-json-compat-utils": "^0.2.3",
"jsonc-eslint-parser": "^3.1.0",
"natural-compare": "^1.4.0",
"synckit": "^0.11.12"
},
"peerDependencies": {
"eslint": ">=9.38.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.27.0",
"@changesets/changelog-github": "^0.6.0",
"@changesets/cli": "^2.28.1",
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
"@eslint/json": "^1.0.0",
"@ota-meshi/eslint-plugin": "^0.20.0",
"@ota-meshi/site-kit-eslint-editor-vue": "^0.2.4",
"@stylistic/eslint-plugin": "^5.7.1",
"@swc-node/register": "^1.11.1",
"@types/mocha": "^10.0.10",
"@types/natural-compare": "^1.4.3",
"@types/node": "^24.0.0",
"@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"c8": "^11.0.0",
"env-cmd": "^11.0.0",
"esbuild": "^0.27.3",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-eslint-plugin": "^7.0.0",
"eslint-plugin-eslint-rule-tester": "^0.6.0",
"eslint-plugin-jsdoc": "^62.0.0",
"eslint-plugin-json-schema-validator": "^6.0.0",
"eslint-plugin-jsonc": "^3.0.0",
"eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-n": "^17.23.2",
"eslint-plugin-node-dependencies": "^2.0.0",
"eslint-plugin-prettier": "^5.2.4",
"eslint-plugin-regexp": "^3.0.0",
"eslint-plugin-vue": "^10.0.0",
"eslint-plugin-yml": "^3.0.0",
"eslint-scope": "^9.1.0",
"events": "^3.3.0",
"globals": "^17.0.0",
"mocha": "^11.1.0",
"pako": "^2.1.0",
"prettier": "^3.5.3",
"semver": "^7.7.1",
"stylelint": "^17.0.0",
"stylelint-config-recommended-vue": "^1.6.0",
"stylelint-config-standard": "^40.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-stylus": "^1.0.0",
"tsdown": "^0.20.0",
"tsx": "^4.21.0",
"typescript": "^5.8.2",
"typescript-eslint": "^8.28.0",
"vite-plugin-eslint4b": "^0.7.0",
"vitepress": "^1.6.3",
"vue-eslint-parser": "^10.1.1"
},
"publishConfig": {
"access": "public"
}
}