$30 off During Our Annual Pro Sale. View Details »

Deep dive into Biome in JSConf 2023

nissy-dev
November 18, 2023
5.3k

Deep dive into Biome in JSConf 2023

nissy-dev

November 18, 2023
Tweet

Transcript

  1. 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
  2. Today’s talk is … Know what is Biome Understand the

    difference between Biome and other tools Feel close to Rust code showing linter code
  3. 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
  4. Latest update Release Intellij plugin 🆕 Add new 15 lint

    rules 🏛 Establish project governance 🚀 Start working CSS parser
  5. Governance Welcome to more contributors Join three new maintainers Collect

    funds from community Open Collective GitHub Sponsor Start thinking about roadmap
  6. Advantages 🚀 Fast: Built with Rust ✅ Simple: Zero configuration

    to get started 🔋 Batteries Included: First class support for TS and JSX … is this all?
  7. 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] "" [] []
  8. 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.. }, }, }, ] }
  9. 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] "}" [] [], ], }, ],
  10. 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] "}" [] [], ], }, ],
  11. 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), }, ],
  12. 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
  13. Our parser is based on Red-Green Tree! This is why

    everything is from scratch Existing AST-based parser tools can't be used
  14. Next, let's look into our linter code! According to the

    difference of our parser our linter is also different...?
  15. 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";
  16. 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({ ... }); } } } } } }
  17. 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>()?; ... } }
  18. 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>()?; ... } }
  19. 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 = ();
  20. 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>()?; ... } }
  21. 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 }
  22. 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 }
  23. 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 }
  24. 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 }
  25. 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 } }
  26. The code is similar than I expected It is not

    necessary to know Rust and Biome in depth from the beginning
  27. Did you feel more familiar with Biome? Welcome to your

    contribution! Learning new languages is fun!
  28. References biomejs.dev Announcing Biome Announcing Rome v10 Rowan Syntax in

    rust-analyzer Rome will be written in Rust 🦀 Rust Dublin October 2023 - Biome