Replies: 8 comments 39 replies
-
File TraversalI will use the ignore crate for file traversal. |
Beta Was this translation helpful? Give feedback.
-
TraversalThe goal here is to maximize multi-core parallelization, where we want to traverse each file, each lint rule and each AST node by using The algorithm:
[0] The untyped node is this gigantic enum |
Beta Was this translation helpful? Give feedback.
-
Lint RuleA Lint rule implements the following trait. pub trait Rule: Default {
const CATEGORY: RuleCategory;
fn run_once(&self, _ctx: &Semantic) -> Vec<Diagnostic> {
vec![]
}
fn run<'a>(&self, _node: &AstNode<'a>, _ctx: &Semantic<'a>) -> Vec<Diagnostic> {
vec![]
}
} These two methods returns a SemanticThe semantic struct wraps everything associated with the AST being called upon. For example scopes, symbols, cfg, trivias etc. There are lint rules which will only act upon semantic data, so I added a AstNode
pub type AstNode<'a> = indextree::Node<SemanticNode<'a>>;
pub struct SemanticNode<'a> {
pub kind: AstKind<'a>,
pub flags: NodeFlags,
/// Associated scope
pub scope_id: ScopeId,
/// Associated control flow node
pub flow_id: Option<FlowNodeId>,
}
bitflags! {
#[derive(Default)]
pub struct NodeFlags: u8 {
const Class = 1 << 0; // If Node is inside a class
const HasImplicitReturn = 1 << 1; // If function implicitly returns on one of codepaths (initialized by binding)
const HasExplicitReturn = 1 << 2; // If `AstKind::FunctionBody` has explicit reachable return on one of codepaths
const HasBreak = 1 << 3; // if `AstKind::WhileStatement` or `AstKind::DoWhileStatement` or `AstKind::ForStatement` has reachable break statement in body.
const HasYield = 1 << 4; // if Function has yield statement in body.
const HasAwait = 1 << 5; // if Function has await statement in body.
const Export = 1 << 6; // Export Declaration
}
} |
Beta Was this translation helpful? Give feedback.
-
Authoring RulesWriting rules looks almost identical to ESLint, except its Rust 😄 use diagnostics::Diagnostic;
use ast::AstKind;
use crate::{AstNode, Rule, RuleCategory, Semantic};
/// https://eslint.org/docs/rules/no-debugger
#[derive(Debug, Default)]
pub struct NoDebugger;
impl Rule for NoDebugger {
const CATEGORY: RuleCategory = RuleCategory::Correctness;
fn run<'a>(&self, node: &AstNode<'a>, _: &Semantic<'a>) -> Vec<Diagnostic> {
let AstKind::DebuggerStatement(stmt) = node.get().kind else { return vec![] };
vec![Diagnostic::NoDebugger(stmt.node.range())]
}
}
#[test]
fn test() {
use crate::rules::LintRule;
use crate::test_util::test;
let pass = ["var test = { debugger: 1 }; test.debugger;"];
let fail = ["if (foo) debugger"];
test(LintRule::NoDebugger(NoDebugger), &pass, &fail);
} Tests are written inside the same file for convenience. |
Beta Was this translation helpful? Give feedback.
-
Rule ConfigurationConfigurations are parsed from json into normal structs. They can accessed from the rule. /// https://eslint.org/docs/latest/rules/no-extra-boolean-cast
#[derive(Debug)]
pub struct NoExtraBooleanCast {
enforce_for_logical_operands: bool,
}
impl Default for NoExtraBooleanCast {
fn default() -> Self {
Self { enforce_for_logical_operands: true }
}
}
impl Rule for NoExtraBooleanCast {
fn from_json(value: serde_json::Value) -> Self {
Self {
enforce_for_logical_operands: value
.get(0)
.and_then(|v| v.get("enforceForLogicalOperands"))
.and_then(|v| v.as_bool())
.unwrap_or_default(),
}
}
} |
Beta Was this translation helpful? Give feedback.
-
ESLint Configuration CommentsFor struct Trivias {
eslint_configuration_comments: BTreeMap<u32, (Rule, Span)>, // keyed by span.start
} The |
Beta Was this translation helpful? Give feedback.
-
Code FixI will use the most basic text editing approach, mainly due to its simplicity. It's interesting to note that rust clippy also uses the text editing approach. |
Beta Was this translation helpful? Give feedback.
-
eslint-plugin-importeslint-plugin-import, a.k.a multi-file linting. This won't be part of the first MVP, but I already have a working solution internally. The code is not really nice to work with so I plan to redesign it later. The working code currently utilizes module resolution (the Rust version of What I haven't figure out yet is to run the module resolution procedure in a memory efficient way. Because every module needs to wait for its dependencies to resolve, peek memory can go up to a 10GB in large monorepos. |
Beta Was this translation helpful? Give feedback.
-
In this discussion I will document how the linter is going to be implemented.
Prioritizations are performance, simplicity and contribution friendly.
This is an initial draft for the first MVP, which may lack some features, such as module resolution.
I'll use structured comments below for better discussion.
Beta Was this translation helpful? Give feedback.
All reactions