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

Deep dive into Biome in JSConf 2023

nissy-dev
November 18, 2023
3.9k

Deep dive into Biome in JSConf 2023

nissy-dev

November 18, 2023
Tweet

Transcript

  1. Deep dive into Biome

    View Slide

  2. 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

    View Slide

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

    View Slide

  4. View Slide

  5. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. Who use Biome…?

    View Slide

  10. View Slide

  11. A. Vercel
    Ant Design
    Unleash
    Tamagui
    … and you?

    View Slide

  12. Why use Biome?

    View Slide

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

    View Slide

  14. Error resilience!
    0:00

    View Slide

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

    View Slide

  16. General parser

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. 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::()?;
    ...
    }
    }

    View Slide

  29. 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::()?;
    ...
    }
    }

    View Slide

  30. 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 = ();

    View Slide

  31. 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::()?;
    ...
    }
    }

    View Slide

  32. 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
    }

    View Slide

  33. 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
    }

    View Slide

  34. 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
    }

    View Slide

  35. 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
    }

    View Slide

  36. 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
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide