Skip to content

Latest commit

 

History

History
180 lines (123 loc) · 5.74 KB

internal-mechanism.md

File metadata and controls

180 lines (123 loc) · 5.74 KB

What the Parser does

The main thing this parser does is parsing the *.svelte file and return an AST that can be parsed by ESLint.
However, this parser does a few other things for a better experience with ESLint integration.

Entry Point

This parser parses *.svelte via parseForESLint() and returns the result to ESLint. This is a requirement for ESLint's custom parser.

See https://eslint.org/docs/latest/developer-guide/working-with-custom-parsers.

Results

ast

Returns the AST of the parses result.

Script AST is ESTree compliant AST by default. However, if you used @typescript-eslint/parser as script parser, it may contain TypeScript AST.
However, the parser assigns a special node SvelteReactiveStatement to the parsed result of $:.
SvelteReactiveStatement is a special node to avoid confusing ESLint check rules with LabeledStatement.

The HTML template part returns a special AST. See AST.md.

The Program node contains tokens and comments. This is a requirement for ESLint's custom parser.

See https://eslint.org/docs/latest/developer-guide/working-with-custom-parsers#the-ast-specification.

services

This parser returns the services returned by the script parser.
In particular, typescript-eslint contains important information such as type information in services. The parser does not edit the services, but there is a trick in parsing the script to get the correct result of the services returned by the script parser.

When parsing the script, the parser does not pass only the <script> part, but generates virtual script code including the script that converted the HTML template into a script, and lets the script parser parse it.

For example, if you enter *.svelte template to listen for input events:

<script lang="ts">
  function inputHandler() {
    // process
  }
</script>

<input on:input={inputHandler} />

Parse the following virtual script code as a script:

function inputHandler() {
  // process
}
function $_render1() {
  inputHandler as (
    e: "input" extends keyof HTMLElementEventMap
      ? HTMLElementEventMap["input"]
      : CustomEvent<any>,
  ) => void;
}

This gives the correct type information to the inputHandler when used with on:input={inputHandler}.

The script AST for the HTML template is then remapped to the template AST.

You can check what happens to virtual scripts in the Online Demo.
https://sveltejs.github.io/svelte-eslint-parser/virtual-script-code/

See also Scope Types section.

scopeManager

This parser returns a ScopeManager instance.
ScopeManager is used in variable analysis such as no-unused-vars and no-undef rules.
See https://eslint.org/docs/latest/developer-guide/scope-manager-interface for details.

The parser will generate a virtual script so that it can parse the correct scope.
For example, when using {#each} and {@const}:

<script lang="ts">
  const array = [1, 2, 3];
</script>

{#each array as e}
  {@const ee = e * 2}
  {ee}
{/each}

Parse the following virtual script code as a script:

const array = [1, 2, 3];
function $_render1() {
  Array.from(array).forEach((e) => {
    const ee = e * 2;
    ee;
  });
}

This ensures that the variable e defined by {#each} is correctly scoped only within {#each}.

Also, this parser returns special results for variables used in $: foo = expression and $count for proper analysis.

It also adds virtual references for variables that are marked specially used in *.svelte (e.g. export let and $ref). This is a hack that is also used in typescript-eslint.
typescript-eslint/typescript-eslint#4508 (comment)

You can also check the results Online DEMO.

visitorKeys

ESLint custom parsers that provide their own AST require visitorKeys to properly traverse the node.

See https://eslint.org/docs/latest/developer-guide/working-with-custom-parsers.

Scope Types

TypeScript's type inference is pretty good, so parsing Svelte as-is gives some wrong type information.

e.g.

export let foo: { bar: number } | null = null;

$: console.log(foo && foo.bar);
                   // ^ never type

(You can see it on TypeScript Online Playground)

In the above code, foo in $: should be object or null in *.svelte, but TypeScript infers that it is null only.

To avoid this problem, the parser generates virtual code and traps statements within $: to function scope. Then restore it to have the correct AST and ScopeManager.

For example:

<script lang="ts">
  export let foo: { bar: number } | null = null;

  $: console.log(foo && foo.bar);

  $: r = foo && foo.bar;

  $: ({ bar: n } = foo || { bar: 42 });
</script>

{foo && foo.bar}

Parse the following virtual script code as a script:

export let foo: { bar: number } | null = null;

$: function $_reactiveStatementScopeFunction1() {
  console.log(foo && foo.bar);
}

$: let r = $_reactiveVariableScopeFunction2();
function $_reactiveVariableScopeFunction2() {
  let $_tmpVar3;
  return ($_tmpVar3 = foo && foo.bar);
}

$: let { bar: n } = $_reactiveVariableScopeFunction4();
function $_reactiveVariableScopeFunction4() {
  let $_tmpVar5;
  return ($_tmpVar5 = foo || { bar: 42 });
}
function $_render6() {
  foo && foo.bar;
}