Slide 1

Slide 1 text

Deep dive into Biome

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Today’s talk is … Know what is Biome Understand the difference between Biome and other tools Feel close to Rust code showing linter code

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Latest update Release Intellij plugin 🆕 Add new 15 lint rules 🏛 Establish project governance 🚀 Start working CSS parser

Slide 7

Slide 7 text

Governance Welcome to more contributors Join three new maintainers Collect funds from community Open Collective GitHub Sponsor Start thinking about roadmap

Slide 8

Slide 8 text

CSS Support Working CSS parser Formatter and linter will come next year

Slide 9

Slide 9 text

Who use Biome…?

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

A. Vercel Ant Design Unleash Tamagui … and you?

Slide 12

Slide 12 text

Why use Biome?

Slide 13

Slide 13 text

Advantages 🚀 Fast: Built with Rust ✅ Simple: Zero configuration to get started 🔋 Batteries Included: First class support for TS and JSX … is this all?

Slide 14

Slide 14 text

Error resilience! 0:00

Slide 15

Slide 15 text

Let's look into the internals of parser! From the viewpoint of error resilience

Slide 16

Slide 16 text

General parser

Slide 17

Slide 17 text

Biome parser Don’t convert code to AST directly Return AST even if the syntax is invalid

Slide 18

Slide 18 text

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] "" [] []

Slide 19

Slide 19 text

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.. }, }, }, ] }

Slide 20

Slide 20 text

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] "}" [] [], ], }, ],

Slide 21

Slide 21 text

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] "}" [] [], ], }, ],

Slide 22

Slide 22 text

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), }, ],

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Our parser is based on Red-Green Tree! This is why everything is from scratch Existing AST-based parser tools can't be used

Slide 25

Slide 25 text

Next, let's look into our linter code! According to the difference of our parser our linter is also different...?

Slide 26

Slide 26 text

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";

Slide 27

Slide 27 text

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({ ... }); } } } } } }

Slide 28

Slide 28 text

Biome implementation impl Rule for EnforceFooBar { // define the AST node which visitor enter type Query = Ast; type State = (); // The main logic for linter // - return Some(()) when to report error // - return None **when not** to report error fn run(ctx: &RuleContext) -> Option { let node = ctx.query(); let parent = node .parent::()? .parent::()?; ... } }

Slide 29

Slide 29 text

Biome implementation // define the AST node which visitor enter type Query = Ast; 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) -> Option { let node = ctx.query(); let parent = node .parent::()? .parent::()?; ... } }

Slide 30

Slide 30 text

Biome implementation // The main logic for linter // - return Some(()) when to report error // - return None **when not** to report error fn run(ctx: &RuleContext) -> Option { let node = ctx.query(); let parent = node .parent::()? .parent::()?; ... } } impl Rule for EnforceFooBar { // define the AST node which visitor enter type Query = Ast; type State = ();

Slide 31

Slide 31 text

Biome implementation impl Rule for EnforceFooBar { // define the AST node which visitor enter type Query = Ast; type State = (); // The main logic for linter // - return Some(()) when to report error // - return None **when not** to report error fn run(ctx: &RuleContext) -> Option { let node = ctx.query(); let parent = node .parent::()? .parent::()?; ... } }

Slide 32

Slide 32 text

Biome implementation fn run(ctx: &RuleContext) -> Option { ... // 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 }

Slide 33

Slide 33 text

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) -> Option { ... // 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 }

Slide 34

Slide 34 text

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) -> Option { ... // check if a `const` variable declaration if parent.is_const() { // Check if variable name is `foo` if node.id().ok()?.text() == "foo" { } } None }

Slide 35

Slide 35 text

Biome implementation fn run(ctx: &RuleContext) -> Option { ... // 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 }

Slide 36

Slide 36 text

Comparison impl Rule for EnforceFooBar { type Query = Ast; type State = (); fn run(ctx: &RuleContext) -> Option { let node = ctx.query(); let parent = node .parent::()? .parent::()?; 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 } }

Slide 37

Slide 37 text

The code is similar than I expected It is not necessary to know Rust and Biome in depth from the beginning

Slide 38

Slide 38 text

Did you feel more familiar with Biome? Welcome to your contribution! Learning new languages is fun!

Slide 39

Slide 39 text

References biomejs.dev Announcing Biome Announcing Rome v10 Rowan Syntax in rust-analyzer Rome will be written in Rust 🦀 Rust Dublin October 2023 - Biome