Share
Embed
Deep dive into Biome in JSConf 2023
The video included in the slides can be accessed here
https://web.archive.org/web/20230328125908/https://rome.tools/blog/2022/11/08/rome-10/
nissy-dev
November 18, 2023
Transcript
39 slides
-
Who am I? Daiki Nishikawa (GitHub: @nissy-dev X: @nissy_dev) Frontend
engineer at Cybozu, Inc. Replace frontend using Next.js App Router I like coding rust 🦀 (also like eating 🦀) Core contributor of Biome -
Today’s talk is … Know what is Biome Understand the
difference between Biome and other tools Feel close to Rust code showing linter code -
What is Biome? Provide toolchains for web development Currently, focus
on linter and formatter Language support: JS, TS, JSON and JSONC Biome is the fork of Rome by community The members of Rome are laid off Rome isn’t maintained anymore -
Latest update Release Intellij plugin 🆕 Add new 15 lint
rules 🏛 Establish project governance 🚀 Start working CSS parser -
Governance Welcome to more contributors Join three new maintainers Collect
funds from community Open Collective GitHub Sponsor Start thinking about roadmap -
Advantages 🚀 Fast: Built with Rust ✅ Simple: Zero configuration
to get started 🔋 Batteries Included: First class support for TS and JSX … is this all? -
Let's look into the internals of parser! From the viewpoint
of error resilience -
Biome parser Don’t convert code to AST directly Return AST
even if the syntax is invalid -
Green Tree・Red Tree Green Tree Immutable tree Node has syntax
kind and text length Include all information about code Red Tree Mutable tree Computed from Green Tree Node has APIs to manipulate trees Example of Green Tree ( const a = 1; ) ` ` 1: [email protected] 2: [email protected] 0: [email protected] 0: [email protected] 0: (empty) 1: [email protected] "const" [] [Whitespace(" 2: [email protected] 0: [email protected] 0: [email protected] 0: [email protected] "a" [] [Whitespace(" 1: (empty) 2: [email protected] 0: [email protected] "=" [] [Whitespace(" ") 1: JS_NUMBER_LITERAL_EXPRESSION@10. 0: [email protected] "1" [ 1: [email protected] ";" [] [] 3: [email protected] "" [] [] -
Cast Red Tree to AST Traverse Red Tree and cast
to AST AST is defined by DSL incompatible with estree Example of AST ( const a = 1; ) ` ` JsVariableStatement { declaration: JsVariableDeclaration { await_token: missing (optional), kind: [email protected] "const" [] [Whitespace( declarators: JsVariableDeclaratorList [ JsVariableDeclarator { id: JsIdentifierBinding { name_token: [email protected] "a" [] [Whites }, variable_annotation: missing (optional) initializer: JsInitializerClause { eq_token: [email protected] "=" [] [Whitespace expression: JsNumberLiteralExpression value_token: JS_NUMBER_LITERAL@10.. }, }, }, ] } -
Handling invalid syntax Allow missing fields Use bogus node A
custom node for invalid syntax Example of AST ( function} ) ` ` items: JsModuleItemList [ JsFunctionDeclaration { async_token: missing (optional), function_token: [email protected] "function" star_token: missing (optional), id: missing (required), type_parameters: missing (optional), parameters: missing (required), return_type_annotation: missing (optional), body: missing (required), }, JsBogusStatement { items: [ [email protected] "}" [] [], ], }, ], -
Handling invalid syntax Allow missing fields Use bogus node A
custom node for invalid syntax Example of AST ( function} ) ` ` id: missing (required), type_parameters: missing (optional), parameters: missing (required), return_type_annotation: missing (optional), body: missing (required), items: JsModuleItemList [ JsFunctionDeclaration { async_token: missing (optional), function_token: [email protected] "function" star_token: missing (optional), }, JsBogusStatement { items: [ [email protected] "}" [] [], ], }, ], -
Handling invalid syntax Allow missing fields Use bogus node A
custom node for invalid syntax Example of AST ( function} ) ` ` JsBogusStatement { items: [ [email protected] "}" [] [], ], }, items: JsModuleItemList [ JsFunctionDeclaration { async_token: missing (optional), function_token: [email protected] "function" star_token: missing (optional), id: missing (required), type_parameters: missing (optional), parameters: missing (required), return_type_annotation: missing (optional), body: missing (required), }, ], -
Red-Green Tree (lossless syntax tree) Great error resilience Fully recoverable
Created by C# compiler team rust-analyzer and swift also use Biome use the forked Rowan Rowan: A library for red-green tree used in rust-analyzer -
Our parser is based on Red-Green Tree! This is why
everything is from scratch Existing AST-based parser tools can't be used -
Next, let's look into our linter code! According to the
difference of our parser our linter is also different...? -
Example: enforce-foo-bar All const variables named "foo" are assigned the
string literal "bar" This is covered in ESLint’s custom rule tutorial // show error! const foo = "foo123"; ↓ // expected const foo = "bar"; -
ESLint implementation create(context) { return { // Performs action in
the function on every variable declarator VariableDeclarator(node) { // Check if a `const` variable declaration if (node.parent.kind === "const") { // Check if variable name is `foo` if (node.id.type === "Identifier" && node.id.name === "foo") { // Check if value of variable is "bar" if (node.init && node.init.type === "Literal" && node.init.value !== "bar") { // report error context.report({ ... }); } } } } } } -
Biome implementation impl Rule for EnforceFooBar { // define the
AST node which visitor enter type Query = Ast<JsVariableDeclarator>; type State = (); // The main logic for linter // - return Some(()) when to report error // - return None **when not** to report error fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { let node = ctx.query(); let parent = node .parent::<JsVariableDeclaratorList>()? .parent::<JsVariableDeclaration>()?; ... } } -
Biome implementation // define the AST node which visitor enter
type Query = Ast<JsVariableDeclarator>; impl Rule for EnforceFooBar { type State = (); // The main logic for linter // - return Some(()) when to report error // - return None **when not** to report error fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { let node = ctx.query(); let parent = node .parent::<JsVariableDeclaratorList>()? .parent::<JsVariableDeclaration>()?; ... } } -
Biome implementation // The main logic for linter // -
return Some(()) when to report error // - return None **when not** to report error fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { let node = ctx.query(); let parent = node .parent::<JsVariableDeclaratorList>()? .parent::<JsVariableDeclaration>()?; ... } } impl Rule for EnforceFooBar { // define the AST node which visitor enter type Query = Ast<JsVariableDeclarator>; type State = (); -
Biome implementation impl Rule for EnforceFooBar { // define the
AST node which visitor enter type Query = Ast<JsVariableDeclarator>; type State = (); // The main logic for linter // - return Some(()) when to report error // - return None **when not** to report error fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { let node = ctx.query(); let parent = node .parent::<JsVariableDeclaratorList>()? .parent::<JsVariableDeclaration>()?; ... } } -
Biome implementation fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { ... //
check if a `const` variable declaration if parent.is_const() { // Check if variable name is `foo` if node.id().ok()?.text() == "foo" { // Check if value of variable is "bar" let init_exp = node.initializer()?.expression().ok()?; let literal_exp = init_exp.as_any_js_literal_expression()?; if literal_exp.as_static_value()?.text() != "bar" { // Report error to Biome // The details of error message is implemented by "diagnostic" method return Some(()); } } } None } -
Biome implementation // check if a `const` variable declaration if
parent.is_const() { // Check if variable name is `foo` if node.id().ok()?.text() == "foo" { fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { ... // Check if value of variable is "bar" let init_exp = node.initializer()?.expression().ok()?; let literal_exp = init_exp.as_any_js_literal_expression()?; if literal_exp.as_static_value()?.text() != "bar" { // Report error to Biome // The details of error message is implemented by "diagnostic" method return Some(()); } } } None } -
Biome implementation // Check if value of variable is "bar"
let init_exp = node.initializer()?.expression().ok()?; let literal_exp = init_exp.as_any_js_literal_expression()?; if literal_exp.as_static_value()?.text() != "bar" { // Report error to Biome // The details of error message is implemented by "diagnostic" method return Some(()); } fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { ... // check if a `const` variable declaration if parent.is_const() { // Check if variable name is `foo` if node.id().ok()?.text() == "foo" { } } None } -
Biome implementation fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { ... //
check if a `const` variable declaration if parent.is_const() { // Check if variable name is `foo` if node.id().ok()?.text() == "foo" { // Check if value of variable is "bar" let init_exp = node.initializer()?.expression().ok()?; let literal_exp = init_exp.as_any_js_literal_expression()?; if literal_exp.as_static_value()?.text() != "bar" { // Report error to Biome // The details of error message is implemented by "diagnostic" method return Some(()); } } } None } -
Comparison impl Rule for EnforceFooBar { type Query = Ast<JsVariableDeclarator>;
type State = (); fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { let node = ctx.query(); let parent = node .parent::<JsVariableDeclaratorList>()? .parent::<JsVariableDeclaration>()?; if parent.is_const() { if node.id().ok()?.text() == "foo" { let init_exp = node.initializer()?.expression().ok()?; let literal_exp = init_exp.as_any_js_literal_expression()?; if literal_exp.as_static_value()?.text() != "bar" { return Some(()); } } } None } } -
The code is similar than I expected It is not
necessary to know Rust and Biome in depth from the beginning -
Did you feel more familiar with Biome? Welcome to your
contribution! Learning new languages is fun! -
References biomejs.dev Announcing Biome Announcing Rome v10 Rowan Syntax in
rust-analyzer Rome will be written in Rust 🦀 Rust Dublin October 2023 - Biome