0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-12 16:59:32 -05:00
denoland-deno/cli/js/40_lint.js

1079 lines
20 KiB
JavaScript
Raw Normal View History

2024-12-04 02:59:39 +01:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { core } from "ext:core/mod.js";
const {
2024-12-03 13:45:31 +01:00
op_lint_get_rule,
op_lint_get_source,
op_lint_report,
2024-12-04 02:59:39 +01:00
} = core.ops;
2024-12-01 04:53:47 +01:00
2024-12-04 12:42:55 +01:00
/** @typedef {{ plugins: Array<{ name: string, rules: Record<string, Deno.LintRule}> }} LintState */
/** @type {LintState} */
const state = {
2024-12-04 12:42:55 +01:00
plugins: [],
};
2024-12-01 04:53:47 +01:00
export class Context {
id;
fileName;
2024-12-04 12:42:55 +01:00
#source = null;
2024-12-01 04:53:47 +01:00
constructor(id, fileName) {
this.id = id;
this.fileName = fileName;
}
2024-12-03 13:45:31 +01:00
source() {
2024-12-04 12:42:55 +01:00
if (this.#source === null) {
this.#source = op_lint_get_source();
}
return this.#source;
2024-12-03 13:45:31 +01:00
}
2024-12-02 02:27:30 +01:00
report(data) {
2024-12-03 03:11:26 +01:00
let start, end;
if (data.node) {
start = data.node.span.start - 1;
end = data.node.span.end - 1;
} else if (data.span) {
start = data.span.start - 1;
end = data.span.end - 1;
} else {
throw new Error(
"Either `node` or `span` must be provided when reporting an error",
);
}
2024-12-02 18:29:30 +01:00
op_lint_report(
this.id,
this.fileName,
data.message,
2024-12-03 03:11:26 +01:00
start,
end,
2024-12-02 18:29:30 +01:00
);
2024-12-01 04:53:47 +01:00
}
}
2024-12-04 04:30:32 +01:00
export function installPlugin(plugin) {
console.log("plugin", plugin);
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");
}
if (typeof plugin.rules !== "object") {
throw new Error("Linter plugin rules must be an object");
}
if (typeof state.plugins[plugin.name] !== "undefined") {
throw new Error(`Linter plugin ${plugin.name} has already been registered`);
}
state.plugins[plugin.name] = plugin.rules;
console.log("Installed plugin", plugin.name, plugin.rules);
}
2024-12-05 01:18:08 +01:00
// Keep in sync with Rust
const AstNode = {
Invalid: 0,
Program: 1,
Import: 2,
// Decls
Class: 12,
Fn: 13,
Var: 14,
// Statements
Block: 20,
Empty: 21,
Debugger: 22,
With: 23,
Return: 24,
Labeled: 25,
Break: 26,
Continue: 27,
If: 28,
Switch: 29,
SwitchCase: 30,
Throw: 31,
Try: 32,
While: 33,
DoWhile: 34,
For: 35,
ForIn: 36,
ForOf: 37,
Decl: 38,
Expr: 39,
// Expressions
This: 40,
Array: 41,
Object: 42,
FnExpr: 43,
Unary: 44,
Update: 45,
Bin: 46,
Assign: 47,
Member: 48,
SuperProp: 49,
Cond: 50,
Call: 51,
New: 52,
Seq: 53,
Ident: 54,
Tpl: 55,
Arrow: 57,
Yield: 59,
StringLiteral: 70,
Bool: 71,
Null: 72,
Num: 73,
BigInt: 74,
Regex: 75,
// Custom
Spread: 83,
ObjProperty: 84,
VarDeclarator: 85,
2024-12-04 16:21:17 +01:00
};
2024-12-05 01:18:08 +01:00
const _ID = Symbol.for("__astId");
class Program {
type = "Program";
body = [];
[_ID] = AstNode.Program;
loc;
constructor(span) {
this.loc = span;
}
}
class VariableDeclaration {
type = "VariableDeclaration";
declarations = [];
[_ID] = AstNode.Var;
loc;
constructor(span) {
this.loc = span;
}
}
class VariableDeclarator {
type = "VariableDeclarator";
id = null;
init = null;
[_ID] = AstNode.VarDeclarator;
loc;
constructor(span) {
this.loc = span;
}
}
class FunctionDeclaration {
type = "FunctionDeclaration";
[_ID] = AstNode.Fn;
generator = false;
async = false;
id = null;
params = [];
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ReturnStatement {
type = "ReturnStatement";
[_ID] = AstNode.Return;
argument = null;
loc;
constructor(span) {
this.loc = span;
}
}
class IfStatement {
type = "IfStatement";
[_ID] = AstNode.If;
test = null;
consequent = null;
alternate = null;
loc;
constructor(span) {
this.loc = span;
}
}
class LabeledStatement {
type = "LabeledStatement";
[_ID] = AstNode.Labeled;
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ThrowStatement {
type = "ThrowStatement";
[_ID] = AstNode.Throw;
argument = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ForStatement {
type = "ForStatement";
[_ID] = AstNode.For;
init = null;
test = null;
update = null;
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ForInStatement {
type = "ForInStatement";
[_ID] = AstNode.ForIn;
left = null;
right = null;
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ForOfStatement {
type = "ForOfStatement";
[_ID] = AstNode.ForOf;
await = false;
left = null;
right = null;
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class WhileStatement {
type = "WhileStatement";
[_ID] = AstNode.While;
test = null;
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ClassDeclaration {
type = "ClassDeclaration";
[_ID] = AstNode.Class;
id = null;
superClass = null;
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class DoWhileStatement {
type = "DoWhileStatement";
[_ID] = AstNode.DoWhile;
test = null;
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class SwitchStatement {
type = "SwitchStatement";
[_ID] = AstNode.Switch;
discriminant = null;
cases = [];
loc;
constructor(span) {
this.loc = span;
}
}
class SwitchCase {
type = "SwitchCase";
[_ID] = AstNode.SwitchCase;
test = null;
consequent = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ExpressionStatement {
type = "ExpressionStatement";
expression = null;
[_ID] = AstNode.Expr;
loc;
constructor(span) {
this.loc = span;
}
}
class NewExpression {
type = "NewExpression";
callee = null;
typeArguments = null;
arguments = [];
[_ID] = AstNode.New;
loc;
constructor(span) {
this.loc = span;
}
}
class UnaryExpression {
type = "UnaryExpression";
argument = null;
operator = null;
[_ID] = AstNode.Unary;
loc;
constructor(span) {
this.loc = span;
}
}
class UpdateExpression {
type = "UpdateExpression";
argument = null;
operator = null;
prefix = false;
[_ID] = AstNode.Update;
loc;
constructor(span) {
this.loc = span;
}
}
class ThisExpression {
type = "ThisExpression";
[_ID] = AstNode.This;
loc;
constructor(span) {
this.loc = span;
}
}
class ArrayExpression {
type = "ArrayExpression";
elements = [];
[_ID] = AstNode.Array;
loc;
constructor(span) {
this.loc = span;
}
}
class ObjectExpression {
type = "ObjectExpression";
properties = [];
[_ID] = AstNode.Object;
loc;
constructor(span) {
this.loc = span;
}
}
class LogicalExpression {
type = "LogicalExpression";
[_ID] = AstNode.Bin;
operator = null;
left = null;
right = null;
loc;
constructor(span) {
this.loc = span;
}
}
class SequenceExpression {
type = "SequenceExpression";
expressions = [];
[_ID] = AstNode.Seq;
loc;
constructor(span) {
this.loc = span;
}
}
class BlockStatement {
type = "BlockStatement";
body = [];
[_ID] = AstNode.Block;
loc;
constructor(span) {
this.loc = span;
}
}
class ContinueStatement {
type = "ContinueStatement";
label = null;
[_ID] = AstNode.Continue;
loc;
constructor(span) {
this.loc = span;
}
}
class BreakStatement {
type = "BreakStatement";
label = null;
[_ID] = AstNode.Break;
loc;
constructor(span) {
this.loc = span;
}
}
class DebuggerStatement {
type = "DebuggerStatement";
[_ID] = AstNode.Debugger;
loc;
constructor(span) {
this.loc = span;
}
}
class MemberExpression {
type = "MemberExpression";
properties = [];
[_ID] = AstNode.Member;
computed = false;
property = null;
object = null;
loc;
constructor(span) {
this.loc = span;
}
}
class CallExpression {
type = "CallExpression";
properties = [];
[_ID] = AstNode.Call;
callee = null;
arguments = [];
loc;
constructor(span) {
this.loc = span;
}
}
class ArrowFunctionExpression {
type = "ArrowFunctionExpression";
[_ID] = AstNode.Arrow;
generator = false;
async = false;
id = null;
params = [];
body = null;
loc;
constructor(span) {
this.loc = span;
}
}
class YieldExpression {
type = "YieldExpression";
[_ID] = AstNode.Yield;
delegate = false;
argument = null;
loc;
constructor(span) {
this.loc = span;
}
}
class ConditionalExpression {
type = "ConditionalExpression";
[_ID] = AstNode.Cond;
test = null;
consequent = null;
alternate = null;
loc;
constructor(span) {
this.loc = span;
}
}
class StringLiteral {
type = "StringLiteral";
[_ID] = AstNode.StringLiteral;
loc;
constructor(span) {
this.loc = span;
}
}
class BooleanLiteral {
type = "BooleanLiteral";
value = false;
[_ID] = AstNode.Bool;
loc;
constructor(span) {
this.loc = span;
}
}
class NullLiteral {
type = "NullLiteral";
value = false;
[_ID] = AstNode.Null;
loc;
constructor(span) {
this.loc = span;
}
}
class NumericLiteral {
type = "NumericLiteral";
value = 0;
[_ID] = AstNode.Num;
loc;
constructor(span) {
this.loc = span;
}
}
class RegExpLiteral {
type = "RegExpLiteral";
value = null;
pattern = "";
flags = "";
[_ID] = AstNode.Regex;
loc;
constructor(span) {
this.loc = span;
}
}
class TemplateLiteral {
type = "TemplateLiteral";
expressions = [];
quasis = [];
[_ID] = AstNode.Tpl;
loc;
constructor(span) {
this.loc = span;
}
}
class Identifier {
type = "Identifier";
[_ID] = AstNode.Ident;
loc;
constructor(span) {
this.loc = span;
}
}
class ObjectProperty {
type = "ObjectProperty";
method = false;
computed = false;
shorthand = false;
key = null;
value = null;
[_ID] = AstNode.ObjProperty;
loc;
constructor(span) {
this.loc = span;
}
}
class SpreadElement {
type = "SpreadElement";
argument = null;
[_ID] = AstNode.Spread;
loc;
constructor(span) {
this.loc = span;
}
}
2024-12-04 16:21:17 +01:00
/**
* @param {Uint8Array} ast
*/
function buildAstFromBinary(ast) {
console.log(ast);
2024-12-05 01:18:08 +01:00
const counts = [];
2024-12-04 16:21:17 +01:00
const stack = [];
2024-12-05 01:18:08 +01:00
for (let i = 0; i < ast.length; i += 14) {
2024-12-04 16:21:17 +01:00
const kind = ast[i];
2024-12-05 01:18:08 +01:00
const flags = ast[i + 1];
const count = ast[i + 2] << ast[i + 3] << ast[i + 4] << ast[i + 5];
const start = ast[i + 6] << ast[i + 7] << ast[i + 8] << ast[i + 9];
const end = ast[i + 10] << ast[i + 11] << ast[i + 12] << ast[i + 13];
const span = [start, end];
let node = null;
switch (kind) {
case AstNode.Program:
node = new Program(span);
break;
case AstNode.Var:
node = new VariableDeclaration(span);
break;
case AstNode.VarDeclarator:
node = new VariableDeclarator(span);
break;
case AstNode.Expr:
node = new ExpressionStatement(span);
break;
case AstNode.This:
node = new ThisExpression(span);
break;
case AstNode.Array:
node = new ArrayExpression(span);
break;
case AstNode.Object:
node = new ObjectExpression(span);
break;
case AstNode.Assign:
node = new ObjectExpression(span);
break;
case AstNode.Member:
node = new MemberExpression(span);
break;
case AstNode.Call:
node = new CallExpression(span);
break;
case AstNode.Seq:
node = new SequenceExpression(span);
break;
case AstNode.ObjProperty:
node = new ObjectProperty(span);
break;
case AstNode.Arrow:
node = new ArrowFunctionExpression(span);
break;
case AstNode.Block:
node = new BlockStatement(span);
break;
case AstNode.StringLiteral:
node = new StringLiteral(span);
break;
case AstNode.Ident:
node = new Identifier(span);
break;
case AstNode.Fn:
node = new FunctionDeclaration(span);
break;
case AstNode.Return:
node = new ReturnStatement(span);
break;
case AstNode.If:
node = new IfStatement(span);
break;
case AstNode.Bin:
node = new LogicalExpression(span);
break;
case AstNode.Unary:
node = new UnaryExpression(span);
break;
case AstNode.Update:
node = new UpdateExpression(span);
break;
case AstNode.For:
node = new ForStatement(span);
break;
case AstNode.Bool:
node = new BooleanLiteral(span);
break;
case AstNode.Null:
node = new NullLiteral(span);
break;
case AstNode.Num:
node = new NumericLiteral(span);
break;
case AstNode.Regex:
node = new RegExpLiteral(span);
break;
case AstNode.ForIn:
node = new ForInStatement(span);
break;
case AstNode.ForOf:
node = new ForOfStatement(span);
break;
case AstNode.While:
node = new WhileStatement(span);
break;
case AstNode.Yield:
node = new YieldExpression(span);
break;
case AstNode.Continue:
node = new ContinueStatement(span);
break;
case AstNode.Break:
node = new BreakStatement(span);
break;
case AstNode.Cond:
node = new ConditionalExpression(span);
break;
case AstNode.Switch:
node = new SwitchStatement(span);
break;
case AstNode.SwitchCase:
node = new SwitchCase(span);
break;
case AstNode.Labeled:
node = new LabeledStatement(span);
break;
case AstNode.DoWhile:
node = new DoWhileStatement(span);
break;
case AstNode.Spread:
node = new SpreadElement(span);
break;
case AstNode.Throw:
node = new ThrowStatement(span);
break;
case AstNode.Debugger:
node = new DebuggerStatement(span);
break;
case AstNode.Tpl:
node = new TemplateLiteral(span);
break;
case AstNode.New:
node = new NewExpression(span);
break;
case AstNode.Class:
node = new ClassDeclaration(span);
break;
default:
throw new Error(`Unknown node: ${kind}`);
}
// append node
if (stack.length > 0) {
const last = stack[stack.length - 1];
const id = last[_ID];
const lastCount = counts[counts.length - 1];
switch (id) {
case AstNode.Program:
case AstNode.Block:
last.body.push(node);
break;
case AstNode.Expr:
last.expression = node;
break;
case AstNode.ObjProperty:
if (lastCount > 1) {
last.value = node;
} else {
last.key = node;
}
break;
case AstNode.Member:
if (lastCount > 1) {
last.property = node;
} else {
last.object = node;
}
break;
case AstNode.Call:
if (lastCount > 1) {
last.arguments.push(node);
} else {
last.callee = node;
}
break;
case AstNode.Seq:
last.expressions.push(node);
break;
case AstNode.Arrow:
// FIXME
break;
case AstNode.Return:
case AstNode.Spread:
case AstNode.Throw:
case AstNode.Unary:
case AstNode.Update:
case AstNode.Yield:
last.argument = node;
break;
case AstNode.If:
case AstNode.Cond:
if (lastCount === 3) {
last.alternate = node;
} else if (lastCount === 2) {
last.consequent = node;
} else {
last.test = node;
}
break;
case AstNode.Bin:
if (lastCount === 2) {
last.right = node;
} else {
last.left = node;
}
break;
case AstNode.For:
if (lastCount === 4) {
last.body = node;
} else if (lastCount === 3) {
last.update = node;
} else if (lastCount === 2) {
last.test = node;
} else if (lastCount === 1) {
last.init = node;
}
break;
case AstNode.ForIn:
case AstNode.ForOf:
if (lastCount === 3) {
last.body = node;
} else if (lastCount === 2) {
last.right = node;
} else {
last.left = node;
}
break;
case AstNode.DoWhile:
case AstNode.While:
if (lastCount === 2) {
last.body = node;
} else {
last.test = node;
}
break;
case AstNode.Break:
case AstNode.Continue:
last.label = node;
break;
case AstNode.Switch:
if (lastCount > 1) {
last.cases.push(node);
} else {
last.discriminant = node;
}
break;
case AstNode.SwitchCase:
if (lastCount > 1) {
last.consequent = node;
} else {
last.test = node;
}
break;
case AstNode.Labeled:
last.body = node;
break;
case AstNode.VarDeclarator:
if (lastCount > 1) {
last.init = node;
} else {
last.id = node;
}
break;
case AstNode.New:
// FIXME
break;
case AstNode.Class:
// FIXME
break;
// Can't happen
case AstNode.Ident:
case AstNode.StringLiteral:
case AstNode.BigInt:
case AstNode.Bool:
case AstNode.Null:
case AstNode.Num:
case AstNode.Regex:
case AstNode.This:
case AstNode.Debugger:
break;
}
// Decrease count
counts[counts.length - 1] = lastCount - 1;
}
if (count > 0) {
stack.push(node);
counts.push(count);
2024-12-04 16:21:17 +01:00
}
}
2024-12-05 01:18:08 +01:00
return stack[0];
2024-12-04 16:21:17 +01:00
}
export function runPluginsForFile(fileName, serializedAst, binary) {
2024-12-05 01:18:08 +01:00
const ast = buildAstFromBinary(binary);
console.log(ast);
// const ast = JSON.parse(serializedAst, (key, value) => {
// if (key === "ctxt") {
// return undefined;
// }
// return value;
// });
2024-12-04 12:42:55 +01:00
/** @type {Record<string, (node: any) => void} */
const mergedVisitor = {};
const destroyFns = [];
// Instantiate and merge visitors. This allows us to only traverse
// the AST once instead of per plugin.
for (let i = 0; i < state.plugins; i++) {
const plugin = state.plugins[i];
for (const name of Object.keys(plugin)) {
const rule = plugin.rules[name];
const id = `${plugin.name}/${ruleName}`;
const ctx = new Context(id, fileName);
const visitor = rule.create(ctx);
for (const name in visitor) {
const prev = mergedVisitor[name];
mergedVisitor[name] = (node) => {
if (typeof prev === "function") {
prev(node);
}
try {
visitor[name](node);
} catch (err) {
throw new Error(`Visitor "${name}" of plugin "${id}" errored`, {
cause: err,
});
}
};
}
mergedVisitor.push({ ctx, visitor, rule });
if (typeof rule.destroy === "function") {
destroyFns.push(() => {
try {
rule.destroy(ctx);
} catch (err) {
throw new Error(`Destroy hook of "${id}" errored`, { cause: err });
}
});
}
}
2024-12-04 12:42:55 +01:00
}
2024-12-04 12:42:55 +01:00
// Traverse ast with all visitors at the same time to avoid traversing
// multiple times.
traverse(ast, mergedVisitor, null);
2024-12-04 12:42:55 +01:00
// Optional: Destroy rules
for (let i = 0; i < destroyFns.length; i++) {
destroyFns[i]();
}
}
2024-12-04 12:42:55 +01:00
/**
* @param {Record<string, any>} ast
* @param {*} visitor
* @param {any | null} parent
* @returns {void}
*/
function traverse(ast, visitor, parent) {
2024-12-03 02:51:29 +01:00
if (!ast || typeof ast !== "object") {
return;
2024-12-01 05:14:46 +01:00
}
2024-12-03 02:51:29 +01:00
// Get node type, accounting for SWC's type property naming
const nodeType = ast.type || (ast.nodeType ? ast.nodeType : null);
// Skip if not a valid AST node
if (!nodeType) {
return;
2024-12-01 05:14:46 +01:00
}
2024-12-03 02:51:29 +01:00
ast.parent = parent;
2024-12-04 12:42:55 +01:00
2024-12-03 02:51:29 +01:00
// Call visitor if it exists for this node type
2024-12-04 12:42:55 +01:00
visitor[nodeType]?.(ast);
2024-12-03 02:51:29 +01:00
// Traverse child nodes
for (const key in ast) {
2024-12-03 03:11:26 +01:00
if (key === "parent" || key === "type") {
2024-12-03 02:51:29 +01:00
continue;
2024-12-01 05:14:46 +01:00
}
2024-12-03 02:51:29 +01:00
const child = ast[key];
if (Array.isArray(child)) {
2024-12-04 12:42:55 +01:00
for (let i = 0; i < child.length; i++) {
const item = child[i];
traverse(item, visitor, ast);
}
} else if (child !== null && typeof child === "object") {
2024-12-03 02:51:29 +01:00
traverse(child, visitor, ast);
2024-12-01 05:14:46 +01:00
}
}
2024-12-01 04:53:47 +01:00
}