2025-01-01 04:12:39 +09:00
|
|
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
// @ts-check
|
|
|
|
|
2024-12-23 08:45:47 +01:00
|
|
|
import {
|
|
|
|
compileSelector,
|
|
|
|
parseSelector,
|
|
|
|
splitSelectors,
|
|
|
|
} from "ext:cli/40_lint_selector.js";
|
2024-12-21 00:58:03 +01:00
|
|
|
import { core, internals } from "ext:core/mod.js";
|
2025-01-14 13:31:02 +01:00
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
const {
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
op_lint_get_source,
|
|
|
|
op_lint_report,
|
2024-12-21 00:58:03 +01:00
|
|
|
op_lint_create_serialized_ast,
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
op_is_cancelled,
|
2024-12-21 00:58:03 +01:00
|
|
|
} = core.ops;
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
let doReport = op_lint_report;
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
// Keep these in sync with Rust
|
|
|
|
const AST_IDX_INVALID = 0;
|
|
|
|
const AST_GROUP_TYPE = 1;
|
|
|
|
/// <type u8>
|
|
|
|
/// <prop offset u32>
|
|
|
|
/// <child idx u32>
|
|
|
|
/// <next idx u32>
|
|
|
|
/// <parent idx u32>
|
|
|
|
const NODE_SIZE = 1 + 4 + 4 + 4 + 4;
|
|
|
|
const PROP_OFFSET = 1;
|
|
|
|
const CHILD_OFFSET = 1 + 4;
|
|
|
|
const NEXT_OFFSET = 1 + 4 + 4;
|
|
|
|
const PARENT_OFFSET = 1 + 4 + 4 + 4;
|
|
|
|
// Span size in buffer: u32 + u32
|
|
|
|
const SPAN_SIZE = 4 + 4;
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
// Keep in sync with Rust
|
|
|
|
// These types are expected to be present on every node. Note that this
|
|
|
|
// isn't set in stone. We could revise this at a future point.
|
2024-12-27 22:46:29 +01:00
|
|
|
const AST_PROP_TYPE = 1;
|
|
|
|
const AST_PROP_PARENT = 2;
|
|
|
|
const AST_PROP_RANGE = 3;
|
|
|
|
const AST_PROP_LENGTH = 4;
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
// Keep in sync with Rust
|
|
|
|
// Each node property is tagged with this enum to denote
|
|
|
|
// what kind of value it holds.
|
|
|
|
/** @enum {number} */
|
|
|
|
const PropFlags = {
|
|
|
|
/** This is an offset to another node */
|
|
|
|
Ref: 0,
|
|
|
|
/** This is an array of offsets to other nodes (like children of a BlockStatement) */
|
|
|
|
RefArr: 1,
|
|
|
|
/**
|
|
|
|
* This is a string id. The actual string needs to be looked up in
|
|
|
|
* the string table that was included in the message.
|
|
|
|
*/
|
|
|
|
String: 2,
|
2025-01-14 13:31:02 +01:00
|
|
|
/**
|
|
|
|
* A numnber field. Numbers are represented as strings internally.
|
|
|
|
*/
|
|
|
|
Number: 3,
|
2024-12-21 00:58:03 +01:00
|
|
|
/** This value is either 0 = false, or 1 = true */
|
2025-01-14 13:31:02 +01:00
|
|
|
Bool: 4,
|
2024-12-21 00:58:03 +01:00
|
|
|
/** No value, it's null */
|
2025-01-14 13:31:02 +01:00
|
|
|
Null: 5,
|
2024-12-21 00:58:03 +01:00
|
|
|
/** No value, it's undefined */
|
2025-01-14 13:31:02 +01:00
|
|
|
Undefined: 6,
|
|
|
|
/** An object */
|
|
|
|
Obj: 7,
|
|
|
|
Regex: 8,
|
|
|
|
BigInt: 9,
|
|
|
|
Array: 10,
|
2024-12-21 00:58:03 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/** @typedef {import("./40_lint_types.d.ts").AstContext} AstContext */
|
|
|
|
/** @typedef {import("./40_lint_types.d.ts").VisitorFn} VisitorFn */
|
|
|
|
/** @typedef {import("./40_lint_types.d.ts").CompiledVisitor} CompiledVisitor */
|
|
|
|
/** @typedef {import("./40_lint_types.d.ts").LintState} LintState */
|
2024-12-23 08:45:47 +01:00
|
|
|
/** @typedef {import("./40_lint_types.d.ts").TransformFn} TransformFn */
|
|
|
|
/** @typedef {import("./40_lint_types.d.ts").MatchContext} MatchContext */
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
/** @type {LintState} */
|
|
|
|
const state = {
|
|
|
|
plugins: [],
|
|
|
|
installedPlugins: new Set(),
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
ignoredRules: new Set(),
|
2024-12-21 00:58:03 +01:00
|
|
|
};
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
function resetState() {
|
|
|
|
state.plugins = [];
|
|
|
|
state.installedPlugins.clear();
|
|
|
|
state.ignoredRules.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This implementation calls into Rust to check if Tokio's cancellation token
|
|
|
|
* has already been canceled.
|
|
|
|
*/
|
|
|
|
class CancellationToken {
|
|
|
|
isCancellationRequested() {
|
|
|
|
return op_is_cancelled();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @implements {Deno.lint.Fixer} */
|
|
|
|
class Fixer {
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node} node
|
|
|
|
* @param {string} text
|
|
|
|
*/
|
|
|
|
insertTextAfter(node, text) {
|
|
|
|
return {
|
|
|
|
range: /** @type {[number, number]} */ ([node.range[1], node.range[1]]),
|
|
|
|
text,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node["range"]} range
|
|
|
|
* @param {string} text
|
|
|
|
*/
|
|
|
|
insertTextAfterRange(range, text) {
|
|
|
|
return {
|
|
|
|
range: /** @type {[number, number]} */ ([range[1], range[1]]),
|
|
|
|
text,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node} node
|
|
|
|
* @param {string} text
|
|
|
|
*/
|
|
|
|
insertTextBefore(node, text) {
|
|
|
|
return {
|
|
|
|
range: /** @type {[number, number]} */ ([node.range[0], node.range[0]]),
|
|
|
|
text,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node["range"]} range
|
|
|
|
* @param {string} text
|
|
|
|
*/
|
|
|
|
insertTextBeforeRange(range, text) {
|
|
|
|
return {
|
|
|
|
range: /** @type {[number, number]} */ ([range[0], range[0]]),
|
|
|
|
text,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node} node
|
|
|
|
*/
|
|
|
|
remove(node) {
|
|
|
|
return {
|
|
|
|
range: node.range,
|
|
|
|
text: "",
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node["range"]} range
|
|
|
|
*/
|
|
|
|
removeRange(range) {
|
|
|
|
return {
|
|
|
|
range,
|
|
|
|
text: "",
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node} node
|
|
|
|
* @param {string} text
|
|
|
|
*/
|
|
|
|
replaceText(node, text) {
|
|
|
|
return {
|
|
|
|
range: node.range,
|
|
|
|
text,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Node["range"]} range
|
|
|
|
* @param {string} text
|
|
|
|
*/
|
|
|
|
replaceTextRange(range, text) {
|
|
|
|
return {
|
|
|
|
range,
|
|
|
|
text,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
/**
|
|
|
|
* Every rule gets their own instance of this class. This is the main
|
|
|
|
* API lint rules interact with.
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
* @implements {Deno.lint.RuleContext}
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
|
|
|
export class Context {
|
|
|
|
id;
|
|
|
|
|
|
|
|
fileName;
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
#source = null;
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
/**
|
|
|
|
* @param {string} id
|
|
|
|
* @param {string} fileName
|
|
|
|
*/
|
|
|
|
constructor(id, fileName) {
|
|
|
|
this.id = id;
|
|
|
|
this.fileName = fileName;
|
|
|
|
}
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
|
|
|
|
source() {
|
|
|
|
if (this.#source === null) {
|
|
|
|
this.#source = op_lint_get_source();
|
|
|
|
}
|
|
|
|
return /** @type {*} */ (this.#source);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.ReportData} data
|
|
|
|
*/
|
|
|
|
report(data) {
|
|
|
|
const range = data.node ? data.node.range : data.range ? data.range : null;
|
|
|
|
if (range == null) {
|
|
|
|
throw new Error(
|
|
|
|
"Either `node` or `range` must be provided when reporting an error",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const start = range[0];
|
|
|
|
const end = range[1];
|
|
|
|
|
2025-02-11 15:24:28 +01:00
|
|
|
/** @type {Deno.lint.FixData[]} */
|
|
|
|
const fixes = [];
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
|
|
|
|
if (typeof data.fix === "function") {
|
|
|
|
const fixer = new Fixer();
|
2025-02-11 15:24:28 +01:00
|
|
|
const result = data.fix(fixer);
|
|
|
|
|
|
|
|
if (Symbol.iterator in result) {
|
|
|
|
for (const fix of result) {
|
|
|
|
fixes.push(fix);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fixes.push(result);
|
|
|
|
}
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
doReport(
|
|
|
|
this.id,
|
|
|
|
data.message,
|
|
|
|
data.hint,
|
|
|
|
start,
|
|
|
|
end,
|
2025-02-11 15:24:28 +01:00
|
|
|
fixes,
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
);
|
|
|
|
}
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
* @param {Deno.lint.Plugin[]} plugins
|
|
|
|
* @param {string[]} exclude
|
|
|
|
*/
|
|
|
|
export function installPlugins(plugins, exclude) {
|
|
|
|
if (Array.isArray(exclude)) {
|
|
|
|
for (let i = 0; i < exclude.length; i++) {
|
|
|
|
state.ignoredRules.add(exclude[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return plugins.map((plugin) => installPlugin(plugin));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Deno.lint.Plugin} plugin
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
function installPlugin(plugin) {
|
2024-12-21 00:58:03 +01:00
|
|
|
if (typeof plugin !== "object") {
|
|
|
|
throw new Error("Linter plugin must be an object");
|
|
|
|
}
|
|
|
|
if (typeof plugin.name !== "string") {
|
|
|
|
throw new Error("Linter plugin name must be a string");
|
|
|
|
}
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
if (!/^[a-z-]+$/.test(plugin.name)) {
|
|
|
|
throw new Error(
|
|
|
|
"Linter plugin name must only contain lowercase letters (a-z) or hyphens (-).",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (plugin.name.startsWith("-") || plugin.name.endsWith("-")) {
|
|
|
|
throw new Error(
|
|
|
|
"Linter plugin name must start and end with a lowercase letter.",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (plugin.name.includes("--")) {
|
|
|
|
throw new Error(
|
|
|
|
"Linter plugin name must not have consequtive hyphens.",
|
|
|
|
);
|
|
|
|
}
|
2024-12-21 00:58:03 +01:00
|
|
|
if (typeof plugin.rules !== "object") {
|
|
|
|
throw new Error("Linter plugin rules must be an object");
|
|
|
|
}
|
|
|
|
if (state.installedPlugins.has(plugin.name)) {
|
|
|
|
throw new Error(`Linter plugin ${plugin.name} has already been registered`);
|
|
|
|
}
|
|
|
|
state.plugins.push(plugin);
|
|
|
|
state.installedPlugins.add(plugin.name);
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
name: plugin.name,
|
|
|
|
ruleNames: Object.keys(plugin.rules),
|
|
|
|
};
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
|
|
|
* @returns {FacadeNode | null}
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
function getNode(ctx, idx) {
|
|
|
|
if (idx === AST_IDX_INVALID) return null;
|
|
|
|
const cached = ctx.nodes.get(idx);
|
|
|
|
if (cached !== undefined) return /** @type {*} */ (cached);
|
|
|
|
|
|
|
|
const node = new FacadeNode(ctx, idx);
|
|
|
|
ctx.nodes.set(idx, /** @type {*} */ (node));
|
|
|
|
return /** @type {*} */ (node);
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the offset of a specific property of a specific node. This will
|
|
|
|
* be used later a lot more for selectors.
|
|
|
|
* @param {Uint8Array} buf
|
|
|
|
* @param {number} search
|
|
|
|
* @param {number} offset
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function findPropOffset(buf, offset, search) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const count = buf[offset];
|
2024-12-21 00:58:03 +01:00
|
|
|
offset += 1;
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
for (let i = 0; i < count; i++) {
|
2024-12-21 00:58:03 +01:00
|
|
|
const maybe = offset;
|
|
|
|
const prop = buf[offset++];
|
|
|
|
const kind = buf[offset++];
|
|
|
|
if (prop === search) return maybe;
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
if (kind === PropFlags.Obj) {
|
2024-12-21 00:58:03 +01:00
|
|
|
const len = readU32(buf, offset);
|
|
|
|
offset += 4;
|
2025-01-14 13:31:02 +01:00
|
|
|
// prop + kind + value
|
|
|
|
offset += len * (1 + 1 + 4);
|
2024-12-21 00:58:03 +01:00
|
|
|
} else {
|
2025-01-14 13:31:02 +01:00
|
|
|
offset += 4;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const INTERNAL_CTX = Symbol("ctx");
|
2025-01-14 13:31:02 +01:00
|
|
|
const INTERNAL_IDX = Symbol("offset");
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
// This class is a facade for all materialized nodes. Instead of creating a
|
|
|
|
// unique class per AST node, we have one class with getters for every
|
|
|
|
// possible node property. This allows us to lazily materialize child node
|
|
|
|
// only when they are needed.
|
2025-01-14 13:31:02 +01:00
|
|
|
class FacadeNode {
|
2024-12-21 00:58:03 +01:00
|
|
|
[INTERNAL_CTX];
|
2025-01-14 13:31:02 +01:00
|
|
|
[INTERNAL_IDX];
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
constructor(ctx, idx) {
|
2024-12-21 00:58:03 +01:00
|
|
|
this[INTERNAL_CTX] = ctx;
|
2025-01-14 13:31:02 +01:00
|
|
|
this[INTERNAL_IDX] = idx;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Logging a class with only getters prints just the class name. This
|
|
|
|
* makes debugging difficult because you don't see any of the properties.
|
|
|
|
* For that reason we'll intercept inspection and serialize the node to
|
|
|
|
* a plain JSON structure which can be logged and allows users to see all
|
|
|
|
* properties and their values.
|
|
|
|
*
|
|
|
|
* This is only expected to be used during development of a rule.
|
|
|
|
* @param {*} _
|
|
|
|
* @param {Deno.InspectOptions} options
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
[Symbol.for("Deno.customInspect")](_, options) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const json = nodeToJson(this[INTERNAL_CTX], this[INTERNAL_IDX]);
|
2024-12-21 00:58:03 +01:00
|
|
|
return Deno.inspect(json, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
[Symbol.for("Deno.lint.toJsValue")]() {
|
2025-01-14 13:31:02 +01:00
|
|
|
return nodeToJson(this[INTERNAL_CTX], this[INTERNAL_IDX]);
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @type {Set<number>} */
|
|
|
|
const appliedGetters = new Set();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add getters for all potential properties found in the message.
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
*/
|
|
|
|
function setNodeGetters(ctx) {
|
|
|
|
if (appliedGetters.size === ctx.strByProp.length) return;
|
|
|
|
|
|
|
|
for (let i = 0; i < ctx.strByProp.length; i++) {
|
|
|
|
const id = ctx.strByProp[i];
|
|
|
|
if (id === 0 || appliedGetters.has(i)) continue;
|
|
|
|
appliedGetters.add(i);
|
|
|
|
|
|
|
|
const name = getString(ctx.strTable, id);
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
Object.defineProperty(FacadeNode.prototype, name, {
|
2024-12-21 00:58:03 +01:00
|
|
|
get() {
|
2025-01-14 13:31:02 +01:00
|
|
|
return readValue(
|
|
|
|
this[INTERNAL_CTX],
|
|
|
|
this[INTERNAL_IDX],
|
|
|
|
i,
|
|
|
|
getNode,
|
|
|
|
);
|
2024-12-21 00:58:03 +01:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
function nodeToJson(ctx, idx) {
|
2024-12-21 00:58:03 +01:00
|
|
|
/** @type {Record<string, any>} */
|
|
|
|
const node = {
|
2025-01-14 13:31:02 +01:00
|
|
|
type: readValue(ctx, idx, AST_PROP_TYPE, nodeToJson),
|
|
|
|
range: readValue(ctx, idx, AST_PROP_RANGE, nodeToJson),
|
2024-12-21 00:58:03 +01:00
|
|
|
};
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
const { buf } = ctx;
|
|
|
|
let offset = readPropOffset(ctx, idx);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
const count = buf[offset++];
|
2025-01-14 13:31:02 +01:00
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
for (let i = 0; i < count; i++) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const prop = buf[offset];
|
|
|
|
const _kind = buf[offset + 1];
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
const name = getString(ctx.strTable, ctx.strByProp[prop]);
|
2025-01-14 13:31:02 +01:00
|
|
|
node[name] = readProperty(ctx, offset, nodeToJson);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
// prop + type + value
|
|
|
|
offset += 1 + 1 + 4;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {AstContext["buf"]} buf
|
|
|
|
* @param {number} idx
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readType(buf, idx) {
|
|
|
|
return buf[idx * NODE_SIZE];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} idx
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
* @returns {Deno.lint.Node["range"]}
|
2025-01-14 13:31:02 +01:00
|
|
|
*/
|
|
|
|
function readSpan(ctx, idx) {
|
|
|
|
let offset = ctx.spansOffset + (idx * SPAN_SIZE);
|
|
|
|
const start = readU32(ctx.buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
const end = readU32(ctx.buf, offset);
|
|
|
|
|
|
|
|
return [start, end];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext["buf"]} buf
|
|
|
|
* @param {number} idx
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readRawPropOffset(buf, idx) {
|
|
|
|
const offset = (idx * NODE_SIZE) + PROP_OFFSET;
|
|
|
|
return readU32(buf, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} idx
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readPropOffset(ctx, idx) {
|
|
|
|
return readRawPropOffset(ctx.buf, idx) + ctx.propsOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext["buf"]} buf
|
|
|
|
* @param {number} idx
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readChild(buf, idx) {
|
|
|
|
const offset = (idx * NODE_SIZE) + CHILD_OFFSET;
|
|
|
|
return readU32(buf, offset);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @param {AstContext["buf"]} buf
|
|
|
|
* @param {number} idx
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readNext(buf, idx) {
|
|
|
|
const offset = (idx * NODE_SIZE) + NEXT_OFFSET;
|
|
|
|
return readU32(buf, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext["buf"]} buf
|
|
|
|
* @param {number} idx
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readParent(buf, idx) {
|
|
|
|
const offset = (idx * NODE_SIZE) + PARENT_OFFSET;
|
|
|
|
return readU32(buf, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext["strTable"]} strTable
|
|
|
|
* @param {number} strId
|
|
|
|
* @returns {RegExp}
|
|
|
|
*/
|
|
|
|
function readRegex(strTable, strId) {
|
|
|
|
const raw = getString(strTable, strId);
|
|
|
|
const idx = raw.lastIndexOf("/");
|
|
|
|
const pattern = raw.slice(1, idx);
|
|
|
|
const flags = idx < raw.length - 1 ? raw.slice(idx + 1) : undefined;
|
|
|
|
|
|
|
|
return new RegExp(pattern, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-12-21 00:58:03 +01:00
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} offset
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {(ctx: AstContext, idx: number) => any} parseNode
|
|
|
|
* @returns {Record<string, any>}
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
function readObject(ctx, offset, parseNode) {
|
|
|
|
const { buf, strTable, strByProp } = ctx;
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
/** @type {Record<string, any>} */
|
|
|
|
const obj = {};
|
|
|
|
|
|
|
|
const count = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
const prop = buf[offset];
|
|
|
|
const name = getString(strTable, strByProp[prop]);
|
|
|
|
obj[name] = readProperty(ctx, offset, parseNode);
|
|
|
|
// name + kind + value
|
|
|
|
offset += 1 + 1 + 4;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} offset
|
|
|
|
* @param {(ctx: AstContext, idx: number) => any} parseNode
|
|
|
|
* @returns {any}
|
|
|
|
*/
|
|
|
|
function readProperty(ctx, offset, parseNode) {
|
|
|
|
const { buf } = ctx;
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
// skip over name
|
|
|
|
const _name = buf[offset++];
|
|
|
|
const kind = buf[offset++];
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
if (kind === PropFlags.Ref) {
|
2024-12-23 08:45:47 +01:00
|
|
|
const value = readU32(buf, offset);
|
2025-01-14 13:31:02 +01:00
|
|
|
return parseNode(ctx, value);
|
2024-12-21 00:58:03 +01:00
|
|
|
} else if (kind === PropFlags.RefArr) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const groupId = readU32(buf, offset);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
const nodes = [];
|
|
|
|
let next = readChild(buf, groupId);
|
|
|
|
while (next > AST_IDX_INVALID) {
|
|
|
|
nodes.push(parseNode(ctx, next));
|
|
|
|
next = readNext(buf, next);
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
2025-01-14 13:31:02 +01:00
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
return nodes;
|
|
|
|
} else if (kind === PropFlags.Bool) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const v = readU32(buf, offset);
|
|
|
|
return v === 1;
|
2024-12-21 00:58:03 +01:00
|
|
|
} else if (kind === PropFlags.String) {
|
|
|
|
const v = readU32(buf, offset);
|
|
|
|
return getString(ctx.strTable, v);
|
2025-01-14 13:31:02 +01:00
|
|
|
} else if (kind === PropFlags.Number) {
|
|
|
|
const v = readU32(buf, offset);
|
|
|
|
return Number(getString(ctx.strTable, v));
|
|
|
|
} else if (kind === PropFlags.BigInt) {
|
|
|
|
const v = readU32(buf, offset);
|
|
|
|
return BigInt(getString(ctx.strTable, v));
|
|
|
|
} else if (kind === PropFlags.Regex) {
|
|
|
|
const v = readU32(buf, offset);
|
|
|
|
return readRegex(ctx.strTable, v);
|
2024-12-21 00:58:03 +01:00
|
|
|
} else if (kind === PropFlags.Null) {
|
|
|
|
return null;
|
|
|
|
} else if (kind === PropFlags.Undefined) {
|
|
|
|
return undefined;
|
2025-01-14 13:31:02 +01:00
|
|
|
} else if (kind === PropFlags.Obj) {
|
|
|
|
const objOffset = readU32(buf, offset) + ctx.propsOffset;
|
|
|
|
return readObject(ctx, objOffset, parseNode);
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(`Unknown prop kind: ${kind}`);
|
|
|
|
}
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
/**
|
|
|
|
* Read a specific property from a node
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {number} idx
|
|
|
|
* @param {number} search
|
|
|
|
* @param {(ctx: AstContext, idx: number) => any} parseNode
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
function readValue(ctx, idx, search, parseNode) {
|
|
|
|
const { buf } = ctx;
|
|
|
|
|
|
|
|
if (search === AST_PROP_TYPE) {
|
|
|
|
const type = readType(buf, idx);
|
|
|
|
return getString(ctx.strTable, ctx.strByType[type]);
|
|
|
|
} else if (search === AST_PROP_RANGE) {
|
|
|
|
return readSpan(ctx, idx);
|
|
|
|
} else if (search === AST_PROP_PARENT) {
|
|
|
|
const parent = readParent(buf, idx);
|
|
|
|
return getNode(ctx, parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
const propOffset = readPropOffset(ctx, idx);
|
|
|
|
|
|
|
|
const offset = findPropOffset(ctx.buf, propOffset, search);
|
|
|
|
if (offset === -1) return undefined;
|
|
|
|
|
|
|
|
return readProperty(ctx, offset, parseNode);
|
|
|
|
}
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
const DECODER = new TextDecoder();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* TODO: Check if it's faster to use the `ArrayView` API instead.
|
|
|
|
* @param {Uint8Array} buf
|
|
|
|
* @param {number} i
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
|
|
|
function readU32(buf, i) {
|
|
|
|
return (buf[i] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) +
|
|
|
|
buf[i + 3];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a string by id and error if it wasn't found
|
|
|
|
* @param {AstContext["strTable"]} strTable
|
|
|
|
* @param {number} id
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
function getString(strTable, id) {
|
|
|
|
const name = strTable.get(id);
|
|
|
|
if (name === undefined) {
|
|
|
|
throw new Error(`Missing string id: ${id}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2024-12-23 08:45:47 +01:00
|
|
|
/** @implements {MatchContext} */
|
|
|
|
class MatchCtx {
|
|
|
|
/**
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {AstContext} ctx
|
2024-12-23 08:45:47 +01:00
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
constructor(ctx) {
|
|
|
|
this.ctx = ctx;
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
2024-12-23 08:45:47 +01:00
|
|
|
* @returns {number}
|
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
getParent(idx) {
|
|
|
|
return readParent(this.ctx.buf, idx);
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
2024-12-23 08:45:47 +01:00
|
|
|
* @returns {number}
|
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
getType(idx) {
|
|
|
|
return readType(this.ctx.buf, idx);
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx - Node idx
|
2024-12-23 08:45:47 +01:00
|
|
|
* @param {number[]} propIds
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} propIdx
|
2024-12-23 08:45:47 +01:00
|
|
|
* @returns {unknown}
|
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
getAttrPathValue(idx, propIds, propIdx) {
|
|
|
|
if (idx === 0) throw -1;
|
|
|
|
|
|
|
|
const { buf, strTable, strByType } = this.ctx;
|
2024-12-23 08:45:47 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
const propId = propIds[propIdx];
|
2024-12-27 22:46:29 +01:00
|
|
|
|
|
|
|
switch (propId) {
|
|
|
|
case AST_PROP_TYPE: {
|
2025-01-14 13:31:02 +01:00
|
|
|
const type = readType(buf, idx);
|
|
|
|
return getString(strTable, strByType[type]);
|
2024-12-27 22:46:29 +01:00
|
|
|
}
|
|
|
|
case AST_PROP_PARENT:
|
|
|
|
case AST_PROP_RANGE:
|
2025-01-14 13:31:02 +01:00
|
|
|
throw -1;
|
2024-12-27 22:46:29 +01:00
|
|
|
}
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
let offset = readPropOffset(this.ctx, idx);
|
|
|
|
|
2024-12-27 22:46:29 +01:00
|
|
|
offset = findPropOffset(buf, offset, propId);
|
2025-01-14 13:31:02 +01:00
|
|
|
if (offset === -1) throw -1;
|
2024-12-23 08:45:47 +01:00
|
|
|
const _prop = buf[offset++];
|
|
|
|
const kind = buf[offset++];
|
|
|
|
|
|
|
|
if (kind === PropFlags.Ref) {
|
|
|
|
const value = readU32(buf, offset);
|
|
|
|
// Checks need to end with a value, not a node
|
2025-01-14 13:31:02 +01:00
|
|
|
if (propIdx === propIds.length - 1) throw -1;
|
|
|
|
return this.getAttrPathValue(value, propIds, propIdx + 1);
|
2024-12-23 08:45:47 +01:00
|
|
|
} else if (kind === PropFlags.RefArr) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const arrIdx = readU32(buf, offset);
|
2024-12-23 08:45:47 +01:00
|
|
|
offset += 4;
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
let count = 0;
|
|
|
|
let child = readChild(buf, arrIdx);
|
|
|
|
while (child > AST_IDX_INVALID) {
|
|
|
|
count++;
|
|
|
|
child = readNext(buf, child);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
propIdx < propIds.length - 1 && propIds[propIdx + 1] === AST_PROP_LENGTH
|
|
|
|
) {
|
2024-12-23 08:45:47 +01:00
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(@marvinhagemeister): Allow traversing into array children?
|
2025-01-14 13:31:02 +01:00
|
|
|
throw -1;
|
|
|
|
} else if (kind === PropFlags.Obj) {
|
|
|
|
// TODO(@marvinhagemeister)
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Cannot traverse into primitives further
|
2025-01-14 13:31:02 +01:00
|
|
|
if (propIdx < propIds.length - 1) throw -1;
|
2024-12-23 08:45:47 +01:00
|
|
|
|
|
|
|
if (kind === PropFlags.String) {
|
|
|
|
const s = readU32(buf, offset);
|
2025-01-14 13:31:02 +01:00
|
|
|
return getString(strTable, s);
|
|
|
|
} else if (kind === PropFlags.Number) {
|
|
|
|
const s = readU32(buf, offset);
|
|
|
|
return Number(getString(strTable, s));
|
|
|
|
} else if (kind === PropFlags.Regex) {
|
|
|
|
const v = readU32(buf, offset);
|
|
|
|
return readRegex(strTable, v);
|
2024-12-23 08:45:47 +01:00
|
|
|
} else if (kind === PropFlags.Bool) {
|
2025-01-14 13:31:02 +01:00
|
|
|
return readU32(buf, offset) === 1;
|
2024-12-23 08:45:47 +01:00
|
|
|
} else if (kind === PropFlags.Null) {
|
|
|
|
return null;
|
|
|
|
} else if (kind === PropFlags.Undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
throw -1;
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} idx
|
|
|
|
* @returns {number}
|
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
getFirstChild(idx) {
|
|
|
|
const siblings = this.getSiblings(idx);
|
|
|
|
return siblings[0] ?? -1;
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
2024-12-23 08:45:47 +01:00
|
|
|
* @returns {number}
|
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
getLastChild(idx) {
|
|
|
|
const siblings = this.getSiblings(idx);
|
|
|
|
return siblings.at(-1) ?? -1;
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
2024-12-23 08:45:47 +01:00
|
|
|
* @returns {number[]}
|
|
|
|
*/
|
2025-01-14 13:31:02 +01:00
|
|
|
getSiblings(idx) {
|
|
|
|
const { buf } = this.ctx;
|
|
|
|
const parent = readParent(buf, idx);
|
|
|
|
|
|
|
|
// Only RefArrays have siblings
|
|
|
|
const parentType = readType(buf, parent);
|
|
|
|
if (parentType !== AST_GROUP_TYPE) {
|
|
|
|
return [];
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const out = [];
|
2025-01-14 13:31:02 +01:00
|
|
|
let child = readChild(buf, parent);
|
|
|
|
while (child > AST_IDX_INVALID) {
|
|
|
|
out.push(child);
|
|
|
|
child = readNext(buf, child);
|
2024-12-23 08:45:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
/**
|
|
|
|
* @param {Uint8Array} buf
|
2025-01-14 13:31:02 +01:00
|
|
|
* @returns {AstContext}
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
|
|
|
function createAstContext(buf) {
|
|
|
|
/** @type {Map<number, string>} */
|
|
|
|
const strTable = new Map();
|
|
|
|
|
|
|
|
// The buffer has a few offsets at the end which allows us to easily
|
|
|
|
// jump to the relevant sections of the message.
|
2025-01-14 13:31:02 +01:00
|
|
|
const propsOffset = readU32(buf, buf.length - 24);
|
|
|
|
const spansOffset = readU32(buf, buf.length - 20);
|
2024-12-21 00:58:03 +01:00
|
|
|
const typeMapOffset = readU32(buf, buf.length - 16);
|
|
|
|
const propMapOffset = readU32(buf, buf.length - 12);
|
|
|
|
const strTableOffset = readU32(buf, buf.length - 8);
|
|
|
|
|
|
|
|
// Offset of the topmost node in the AST Tree.
|
|
|
|
const rootOffset = readU32(buf, buf.length - 4);
|
|
|
|
|
|
|
|
let offset = strTableOffset;
|
|
|
|
const stringCount = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
let strId = 0;
|
2024-12-21 00:58:03 +01:00
|
|
|
for (let i = 0; i < stringCount; i++) {
|
|
|
|
const len = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
const strBytes = buf.slice(offset, offset + len);
|
|
|
|
offset += len;
|
|
|
|
const s = DECODER.decode(strBytes);
|
2025-01-14 13:31:02 +01:00
|
|
|
strTable.set(strId, s);
|
|
|
|
strId++;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (strTable.size !== stringCount) {
|
|
|
|
throw new Error(
|
|
|
|
`Could not deserialize string table. Expected ${stringCount} items, but got ${strTable.size}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = typeMapOffset;
|
|
|
|
const typeCount = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
const typeByStr = new Map();
|
|
|
|
const strByType = new Array(typeCount).fill(0);
|
|
|
|
for (let i = 0; i < typeCount; i++) {
|
|
|
|
const v = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
strByType[i] = v;
|
|
|
|
typeByStr.set(strTable.get(v), i);
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = propMapOffset;
|
|
|
|
const propCount = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
const propByStr = new Map();
|
|
|
|
const strByProp = new Array(propCount).fill(0);
|
|
|
|
for (let i = 0; i < propCount; i++) {
|
|
|
|
const v = readU32(buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
strByProp[i] = v;
|
|
|
|
propByStr.set(strTable.get(v), i);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @type {AstContext} */
|
|
|
|
const ctx = {
|
|
|
|
buf,
|
|
|
|
strTable,
|
|
|
|
rootOffset,
|
2025-01-14 13:31:02 +01:00
|
|
|
spansOffset,
|
|
|
|
propsOffset,
|
2024-12-21 00:58:03 +01:00
|
|
|
nodes: new Map(),
|
|
|
|
strTableOffset,
|
|
|
|
strByProp,
|
|
|
|
strByType,
|
|
|
|
typeByStr,
|
|
|
|
propByStr,
|
2025-01-14 13:31:02 +01:00
|
|
|
matcher: /** @type {*} */ (null),
|
2024-12-21 00:58:03 +01:00
|
|
|
};
|
2025-01-14 13:31:02 +01:00
|
|
|
ctx.matcher = new MatchCtx(ctx);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
setNodeGetters(ctx);
|
|
|
|
|
|
|
|
// DEV ONLY: Enable this to inspect the buffer message
|
|
|
|
// _dump(ctx);
|
|
|
|
|
|
|
|
return ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {*} _node
|
|
|
|
*/
|
|
|
|
const NOOP = (_node) => {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kick off the actual linting process of JS plugins.
|
|
|
|
* @param {string} fileName
|
|
|
|
* @param {Uint8Array} serializedAst
|
|
|
|
*/
|
|
|
|
export function runPluginsForFile(fileName, serializedAst) {
|
|
|
|
const ctx = createAstContext(serializedAst);
|
|
|
|
|
2024-12-23 08:45:47 +01:00
|
|
|
/** @type {Map<string, CompiledVisitor["info"]>}>} */
|
2024-12-21 00:58:03 +01:00
|
|
|
const bySelector = new Map();
|
|
|
|
|
|
|
|
const destroyFns = [];
|
|
|
|
|
|
|
|
// Instantiate and merge visitors. This allows us to only traverse
|
|
|
|
// the AST once instead of per plugin. When ever we enter or exit a
|
|
|
|
// node we'll call all visitors that match.
|
|
|
|
for (let i = 0; i < state.plugins.length; i++) {
|
|
|
|
const plugin = state.plugins[i];
|
|
|
|
|
|
|
|
for (const name of Object.keys(plugin.rules)) {
|
|
|
|
const rule = plugin.rules[name];
|
|
|
|
const id = `${plugin.name}/${name}`;
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
|
|
|
|
// Check if this rule is excluded
|
|
|
|
if (state.ignoredRules.has(id)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
const ctx = new Context(id, fileName);
|
|
|
|
const visitor = rule.create(ctx);
|
|
|
|
|
|
|
|
// deno-lint-ignore guard-for-in
|
|
|
|
for (let key in visitor) {
|
|
|
|
const fn = visitor[key];
|
|
|
|
if (fn === undefined) continue;
|
|
|
|
|
|
|
|
// Support enter and exit callbacks on a visitor.
|
|
|
|
// Exit callbacks are marked by having `:exit` at the end.
|
|
|
|
let isExit = false;
|
|
|
|
if (key.endsWith(":exit")) {
|
|
|
|
isExit = true;
|
|
|
|
key = key.slice(0, -":exit".length);
|
|
|
|
}
|
|
|
|
|
2024-12-23 08:45:47 +01:00
|
|
|
const selectors = splitSelectors(key);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2024-12-23 08:45:47 +01:00
|
|
|
for (let j = 0; j < selectors.length; j++) {
|
|
|
|
const key = selectors[j];
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2024-12-23 08:45:47 +01:00
|
|
|
let info = bySelector.get(key);
|
|
|
|
if (info === undefined) {
|
|
|
|
info = { enter: NOOP, exit: NOOP };
|
|
|
|
bySelector.set(key, info);
|
|
|
|
}
|
|
|
|
const prevFn = isExit ? info.exit : info.enter;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {*} node
|
|
|
|
*/
|
|
|
|
const wrapped = (node) => {
|
|
|
|
prevFn(node);
|
|
|
|
|
|
|
|
try {
|
|
|
|
fn(node);
|
|
|
|
} catch (err) {
|
|
|
|
throw new Error(`Visitor "${name}" of plugin "${id}" errored`, {
|
|
|
|
cause: err,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (isExit) {
|
|
|
|
info.exit = wrapped;
|
|
|
|
} else {
|
|
|
|
info.enter = wrapped;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof rule.destroy === "function") {
|
|
|
|
const destroyFn = rule.destroy.bind(rule);
|
|
|
|
destroyFns.push(() => {
|
|
|
|
try {
|
|
|
|
destroyFn(ctx);
|
|
|
|
} catch (err) {
|
|
|
|
throw new Error(`Destroy hook of "${id}" errored`, { cause: err });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-23 08:45:47 +01:00
|
|
|
// Create selectors
|
|
|
|
/** @type {TransformFn} */
|
|
|
|
const toElem = (str) => {
|
|
|
|
const id = ctx.typeByStr.get(str);
|
|
|
|
return id === undefined ? 0 : id;
|
|
|
|
};
|
|
|
|
/** @type {TransformFn} */
|
|
|
|
const toAttr = (str) => {
|
|
|
|
const id = ctx.propByStr.get(str);
|
|
|
|
return id === undefined ? 0 : id;
|
|
|
|
};
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
/** @type {CompiledVisitor[]} */
|
|
|
|
const visitors = [];
|
|
|
|
for (const [sel, info] of bySelector.entries()) {
|
2024-12-23 08:45:47 +01:00
|
|
|
// Selectors are already split here.
|
|
|
|
// TODO(@marvinhagemeister): Avoid array allocation (not sure if that matters)
|
|
|
|
const parsed = parseSelector(sel, toElem, toAttr)[0];
|
|
|
|
const matcher = compileSelector(parsed);
|
|
|
|
|
|
|
|
visitors.push({ info, matcher });
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
const token = new CancellationToken();
|
2024-12-21 00:58:03 +01:00
|
|
|
// Traverse ast with all visitors at the same time to avoid traversing
|
|
|
|
// multiple times.
|
|
|
|
try {
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
traverse(ctx, visitors, ctx.rootOffset, token);
|
2024-12-21 00:58:03 +01:00
|
|
|
} finally {
|
|
|
|
ctx.nodes.clear();
|
|
|
|
|
|
|
|
// Optional: Destroy rules
|
|
|
|
for (let i = 0; i < destroyFns.length; i++) {
|
|
|
|
destroyFns[i]();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
* @param {CompiledVisitor[]} visitors
|
2025-01-14 13:31:02 +01:00
|
|
|
* @param {number} idx
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
* @param {CancellationToken} cancellationToken
|
2024-12-21 00:58:03 +01:00
|
|
|
*/
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
function traverse(ctx, visitors, idx, cancellationToken) {
|
2025-01-14 13:31:02 +01:00
|
|
|
if (idx === AST_IDX_INVALID) return;
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
if (cancellationToken.isCancellationRequested()) return;
|
2024-12-23 08:45:47 +01:00
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
const { buf } = ctx;
|
2025-01-14 13:31:02 +01:00
|
|
|
const nodeType = readType(ctx.buf, idx);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
/** @type {VisitorFn[] | null} */
|
|
|
|
let exits = null;
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
// Only visit if it's an actual node
|
|
|
|
if (nodeType !== AST_GROUP_TYPE) {
|
|
|
|
// Loop over visitors and check if any selector matches
|
|
|
|
for (let i = 0; i < visitors.length; i++) {
|
|
|
|
const v = visitors[i];
|
|
|
|
if (v.matcher(ctx.matcher, idx)) {
|
|
|
|
if (v.info.exit !== NOOP) {
|
|
|
|
if (exits === null) {
|
|
|
|
exits = [v.info.exit];
|
|
|
|
} else {
|
|
|
|
exits.push(v.info.exit);
|
|
|
|
}
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
if (v.info.enter !== NOOP) {
|
|
|
|
const node = /** @type {*} */ (getNode(ctx, idx));
|
|
|
|
v.info.enter(node);
|
|
|
|
}
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2025-01-14 13:31:02 +01:00
|
|
|
const childIdx = readChild(buf, idx);
|
|
|
|
if (childIdx > AST_IDX_INVALID) {
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
traverse(ctx, visitors, childIdx, cancellationToken);
|
2025-01-14 13:31:02 +01:00
|
|
|
}
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
const nextIdx = readNext(buf, idx);
|
|
|
|
if (nextIdx > AST_IDX_INVALID) {
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
traverse(ctx, visitors, nextIdx, cancellationToken);
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (exits !== null) {
|
|
|
|
for (let i = 0; i < exits.length; i++) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const node = /** @type {*} */ (getNode(ctx, idx));
|
2024-12-21 00:58:03 +01:00
|
|
|
exits[i](node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is useful debugging helper to display the buffer's contents.
|
|
|
|
* @param {AstContext} ctx
|
|
|
|
*/
|
|
|
|
function _dump(ctx) {
|
|
|
|
const { buf, strTableOffset, strTable, strByType, strByProp } = ctx;
|
|
|
|
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(strTable);
|
|
|
|
|
|
|
|
for (let i = 0; i < strByType.length; i++) {
|
|
|
|
const v = strByType[i];
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
if (v > 0) console.log(" > type:", i, getString(ctx.strTable, v), v);
|
|
|
|
}
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log();
|
|
|
|
for (let i = 0; i < strByProp.length; i++) {
|
|
|
|
const v = strByProp[i];
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
if (v > 0) console.log(" > prop:", i, getString(ctx.strTable, v), v);
|
|
|
|
}
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log();
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log();
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
let idx = 0;
|
|
|
|
while (idx < (strTableOffset / NODE_SIZE)) {
|
|
|
|
const type = readType(buf, idx);
|
|
|
|
const child = readChild(buf, idx);
|
|
|
|
const next = readNext(buf, idx);
|
|
|
|
const parent = readParent(buf, idx);
|
|
|
|
const range = readSpan(ctx, idx);
|
|
|
|
|
|
|
|
const name = type === AST_IDX_INVALID
|
|
|
|
? "<invalid>"
|
|
|
|
: type === AST_GROUP_TYPE
|
|
|
|
? "<group>"
|
|
|
|
: getString(ctx.strTable, ctx.strByType[type]);
|
2024-12-21 00:58:03 +01:00
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
2025-01-14 13:31:02 +01:00
|
|
|
console.log(`${name}, idx: ${idx}, type: ${type}`);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
2025-01-14 13:31:02 +01:00
|
|
|
console.log(` child: ${child}, next: ${next}, parent: ${parent}`);
|
2024-12-21 00:58:03 +01:00
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
2025-01-14 13:31:02 +01:00
|
|
|
console.log(` range: ${range[0]}, ${range[1]}`);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
const rawOffset = readRawPropOffset(ctx.buf, idx);
|
|
|
|
let propOffset = readPropOffset(ctx, idx);
|
|
|
|
const count = buf[propOffset++];
|
2024-12-21 00:58:03 +01:00
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
2025-01-14 13:31:02 +01:00
|
|
|
console.log(
|
|
|
|
` prop count: ${count}, prop offset: ${propOffset} raw offset: ${rawOffset}`,
|
|
|
|
);
|
2024-12-21 00:58:03 +01:00
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const prop = buf[propOffset++];
|
|
|
|
const kind = buf[propOffset++];
|
2024-12-21 00:58:03 +01:00
|
|
|
const name = getString(ctx.strTable, ctx.strByProp[prop]);
|
|
|
|
|
|
|
|
let kindName = "unknown";
|
|
|
|
for (const k in PropFlags) {
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
if (kind === PropFlags[k]) {
|
|
|
|
kindName = k;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-14 13:31:02 +01:00
|
|
|
const v = readU32(buf, propOffset);
|
|
|
|
propOffset += 4;
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
if (kind === PropFlags.Ref) {
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(` ${name}: ${v} (${kindName}, ${prop})`);
|
|
|
|
} else if (kind === PropFlags.RefArr) {
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
2025-01-14 13:31:02 +01:00
|
|
|
console.log(` ${name}: RefArray: ${v}, (${kindName}, ${prop})`);
|
2024-12-21 00:58:03 +01:00
|
|
|
} else if (kind === PropFlags.Bool) {
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(` ${name}: ${v} (${kindName}, ${prop})`);
|
|
|
|
} else if (kind === PropFlags.String) {
|
2025-01-14 13:31:02 +01:00
|
|
|
const raw = getString(ctx.strTable, v);
|
2024-12-21 00:58:03 +01:00
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
2025-01-14 13:31:02 +01:00
|
|
|
console.log(` ${name}: ${raw} (${kindName}, ${prop})`);
|
|
|
|
} else if (kind === PropFlags.Number) {
|
|
|
|
const raw = getString(ctx.strTable, v);
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(` ${name}: ${raw} (${kindName}, ${prop})`);
|
|
|
|
} else if (kind === PropFlags.Regex) {
|
|
|
|
const raw = getString(ctx.strTable, v);
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(` ${name}: ${raw} (${kindName}, ${prop})`);
|
2024-12-21 00:58:03 +01:00
|
|
|
} else if (kind === PropFlags.Null) {
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(` ${name}: null (${kindName}, ${prop})`);
|
|
|
|
} else if (kind === PropFlags.Undefined) {
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(` ${name}: undefined (${kindName}, ${prop})`);
|
2025-01-14 13:31:02 +01:00
|
|
|
} else if (kind === PropFlags.BigInt) {
|
|
|
|
const raw = getString(ctx.strTable, v);
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(` ${name}: ${raw} (${kindName}, ${prop})`);
|
|
|
|
} else if (kind === PropFlags.Obj) {
|
|
|
|
let offset = v + ctx.propsOffset;
|
|
|
|
const count = readU32(ctx.buf, offset);
|
|
|
|
offset += 4;
|
|
|
|
|
|
|
|
// @ts-ignore dump fn
|
|
|
|
// deno-lint-ignore no-console
|
|
|
|
console.log(
|
|
|
|
` ${name}: Object (${count}) (${kindName}, ${prop}), raw offset ${v}`,
|
|
|
|
);
|
|
|
|
|
|
|
|
// TODO(@marvinhagemeister): Show object
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
}
|
2025-01-14 13:31:02 +01:00
|
|
|
|
|
|
|
idx++;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
// These are captured by Rust and called when plugins need to be loaded
|
|
|
|
// or run.
|
|
|
|
internals.installPlugins = installPlugins;
|
|
|
|
internals.runPluginsForFile = runPluginsForFile;
|
|
|
|
internals.resetState = resetState;
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
/**
|
|
|
|
* @param {LintPlugin} plugin
|
|
|
|
* @param {string} fileName
|
|
|
|
* @param {string} sourceText
|
|
|
|
*/
|
|
|
|
function runLintPlugin(plugin, fileName, sourceText) {
|
|
|
|
installPlugin(plugin);
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
const diagnostics = [];
|
|
|
|
doReport = (id, message, hint, start, end, fix) => {
|
|
|
|
diagnostics.push({
|
|
|
|
id,
|
|
|
|
message,
|
|
|
|
hint,
|
|
|
|
range: [start, end],
|
|
|
|
fix,
|
|
|
|
});
|
|
|
|
};
|
2024-12-21 00:58:03 +01:00
|
|
|
try {
|
2025-01-14 13:31:02 +01:00
|
|
|
const serializedAst = op_lint_create_serialized_ast(fileName, sourceText);
|
|
|
|
|
2024-12-21 00:58:03 +01:00
|
|
|
runPluginsForFile(fileName, serializedAst);
|
|
|
|
} finally {
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
resetState();
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
doReport = op_lint_report;
|
|
|
|
return diagnostics;
|
2024-12-21 00:58:03 +01:00
|
|
|
}
|
|
|
|
|
feat(lint): add JavaScript plugin support (#27203)
This commit adds an unstable lint plugin API.
Plugins are specified in the `deno.json` file under
`lint.plugins` option like so:
```
{
"lint": {
"plugins": [
"./plugins/my-plugin.ts",
"jsr:@deno/lint-plugin1",
"npm:@deno/lint-plugin2"
]
}
}
```
The API is considered unstable and might be subject
to changes in the future.
Plugin API was modelled after ESLint API for the
most part, but there are no guarantees for compatibility.
The AST format exposed to plugins is closely modelled
after the AST that `typescript-eslint` uses.
Lint plugins use the visitor pattern and can add
diagnostics like so:
```
export default {
name: "lint-plugin",
rules: {
"plugin-rule": {
create(context) {
return {
Identifier(node) {
if (node.name === "a") {
context.report({
node,
message: "should be b",
fix(fixer) {
return fixer.replaceText(node, "_b");
},
});
}
},
};
},
},
},
} satisfies Deno.lint.Plugin;
```
Besides reporting errors (diagnostics) plugins can provide
automatic fixes that use text replacement to apply changes.
---------
Co-authored-by: Marvin Hagemeister <marvin@deno.com>
Co-authored-by: David Sherret <dsherret@gmail.com>
2025-02-05 16:59:24 +01:00
|
|
|
Deno.lint.runPlugin = runLintPlugin;
|