sveast 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -20
- package/core.d.ts +1757 -0
- package/core.js +1 -0
- package/entities.d.ts +10 -0
- package/entities.js +1 -0
- package/index.d.ts +1575 -1307
- package/index.js +1 -1
- package/is-valid-type.js +1 -1
- package/package.json +16 -1
- package/parse-module.js +1 -1
- package/parse.js +1 -2
- package/shared-3ca5cgq8.js +2 -0
- package/shared-7q7hyetp.js +3 -0
- package/shared-c2k7d7rg.js +2 -0
- package/shared-cx7c0gk3.js +10 -0
- package/shared-da2hc1fx.js +2 -0
- package/shared-kqcn291j.js +2 -0
- package/shared-p7c7qbej.js +2 -0
- package/shared-t6kshmn4.js +12 -0
- package/shared-xvd609sh.js +2 -0
- package/shared-xw9zc4rz.js +2 -0
- package/typescript.d.ts +9 -0
- package/typescript.js +1 -0
- package/walk.js +1 -0
- package/shared-rhdkc546.js +0 -12
- package/shared-xbh87rns.js +0 -10
package/README.md
CHANGED
|
@@ -34,7 +34,8 @@ sveast is a drop-in for `parse` in most tools: change the import, and pass `loc:
|
|
|
34
34
|
| `loc`, `name_loc` | Always | With `loc: true`; otherwise only `start`/`end` offsets, for a faster parse and a smaller AST |
|
|
35
35
|
| Errors | `CompileError` | `ParseError`: same `code`, `message`, `position`, `start`, `end` and `frame`; no `filename`; `reason`, the message without its link |
|
|
36
36
|
| AST formats | Modern, legacy (`modern: false`), error-tolerant (`loose`) | Modern |
|
|
37
|
-
| Scope | Parsing, `parseCss`, analysis, compilation | Parsing (`parse`, `parseModule`, `isValidType`) |
|
|
37
|
+
| Scope | Parsing, `parseCss`, analysis, compilation | Parsing (`parse`, `parseModule`, `isValidType`), and walking the AST (`walk`, `visitorKeys`) |
|
|
38
|
+
| Non-ASCII identifiers | acorn's tables, Unicode 17 | The engine's own Unicode data, which is smaller to ship: the same as acorn's in Node 24 and Bun; an engine on another Unicode version differs on the letters added in between |
|
|
38
39
|
| TypeScript-only errors | Reported, e.g. modifier order or initializers in ambient contexts | Not reported: 108 of the 2,449 TypeScript conformance tests acorn-typescript rejects still parse |
|
|
39
40
|
|
|
40
41
|
## API
|
|
@@ -45,6 +46,8 @@ sveast is a drop-in for `parse` in most tools: change the import, and pass `loc:
|
|
|
45
46
|
|:---|:---|
|
|
46
47
|
| `loc` | Add `loc` (line and column) to script and expression nodes, and `name_loc` to elements, attributes and directives, as svelte does. Default `false`: they cost time and memory, and most tools only need `start`/`end`. |
|
|
47
48
|
| `css` | Parse `<style>` into rules and selectors. With `false`, `css` keeps its `start`, `end` and `content`, but `children` and `comments` are empty and CSS syntax errors aren't reported. Default `true`. |
|
|
49
|
+
| `script` | Parse each `<script>`'s JavaScript or TypeScript. With `false`, `instance` and `module` keep their attributes, `start` and `end`, and `content` is a `Program` with its `start` and `end` but an empty `body`; the scripts' comments aren't in `comments`, and their syntax errors aren't reported. Expressions in the markup are still parsed. For a pass that only reads the markup, such as collecting which components a file renders and with what props; on the benchmark corpus, it parses 3.4× faster. Default `true`. |
|
|
50
|
+
| `comments` | Collect JavaScript comments, in scripts, expressions and tags. With `false`, `comments` is empty and no node has `leadingComments` or `trailingComments`, including the HTML comment before a `<script>` that svelte copies into its `content.leadingComments`. HTML comments in the markup and CSS comments are kept. For tools that never read comments; on Carbon's components, it parses 3 to 6% faster and the ASTs take 8% less memory. Default `true`. |
|
|
48
51
|
|
|
49
52
|
With `loc: true`, the result equals svelte's. With the defaults, it's svelte's without `loc` and `name_loc`.
|
|
50
53
|
|
|
@@ -52,15 +55,43 @@ A syntax error throws a `ParseError` with svelte's `code` (e.g. `"block_unclosed
|
|
|
52
55
|
|
|
53
56
|
### `parseModule(source, options?) => Program`
|
|
54
57
|
|
|
55
|
-
Parses a JavaScript or TypeScript module, such as a `.ts` file a component imports, the way a component's `<script>` is parsed: estree plus TypeScript nodes, with comments attached as `leadingComments`/`trailingComments`. Options: `typescript` and `loc`, both default `false`.
|
|
58
|
+
Parses a JavaScript or TypeScript module, such as a `.ts` file a component imports, the way a component's `<script>` is parsed: estree plus TypeScript nodes, with comments attached as `leadingComments`/`trailingComments`. Options: `typescript` and `loc`, both default `false`, and `comments`, default `true`; with `false`, no node has `leadingComments` or `trailingComments`.
|
|
56
59
|
|
|
57
|
-
### `
|
|
60
|
+
### `createParser(support?) => { parse, parseModule }` from `sveast/core`
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
`parse` and `parseModule` without the two parts most tools can do without: the TypeScript plugin and the table of HTML's named character references. Together they're about a third of a bundle: `parse` and `parseModule` from `sveast` minify to 57.3 kB gzipped, and from `createParser()` to 38.7 kB. Pass back the parts you need; with both, the parsers are the same as `sveast`'s.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { createParser } from "sveast/core";
|
|
66
|
+
import { typescript } from "sveast/typescript";
|
|
67
|
+
|
|
68
|
+
const { parse, parseModule } = createParser({ typescript });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Support | Without it |
|
|
72
|
+
|:---|:---|
|
|
73
|
+
| `typescript` from `sveast/typescript` (+8.8 kB gzipped) | A component with `<script lang="ts">`, or `parseModule` with `typescript: true`, throws an `Error`, not a `ParseError`: a missing import, not a syntax error, and never a JavaScript AST of TypeScript |
|
|
74
|
+
| `entities` from `sveast/entities` (+10.0 kB gzipped) | Text and attribute values decode numeric references, such as `©`, and `&`, `'`, `>`, `<` and `"`, but leave other names, such as `©`, as written, so `Text.data` differs from svelte's where the source uses them |
|
|
75
|
+
|
|
76
|
+
`sveast/core` also exports `ParseError`, the same class as `sveast`'s, and the types.
|
|
77
|
+
|
|
78
|
+
### `isValidType(text, options?) => boolean`
|
|
79
|
+
|
|
80
|
+
Whether `text` is exactly one TypeScript type, such as a JSDoc `{"sm" | "lg"}` a tool is about to copy into a `.d.ts`. The text is parsed as a type on its own, not wrapped in a statement, so a `;`, a line break or a `}` in it can't end the type and smuggle in a statement: `string; let x = 1` and `{ a: string } }` are `false`. Whitespace and comments around the type are allowed. Results aren't cached; memoize if you check the same text often.
|
|
81
|
+
|
|
82
|
+
A `//` comment runs to the end of the line, so `string // the size` is a valid type but breaks `CustomEvent<${text}>`. Pass `inline: true` when the text goes before more code on the same line: it's then also `false` unless every `//` comment in the text ends with a line break. Block comments and `//` inside strings, as in `"http://a" | "https://b"`, are fine either way.
|
|
83
|
+
|
|
84
|
+
### `walk(node, visitor) => void`
|
|
85
|
+
|
|
86
|
+
Visits `node` and every node under it, depth-first and in source order, calling `visitor.enter(node, parent, key, index)` before a node's children and `visitor.leave(node, parent, key, index)` after them. `parent[key]` is the node, or `parent[key][index]` when the field is an array; all three are `null` for the node you pass. Return `false` from `enter` to skip the node's children; `leave` is still called, so a stack you push in `enter` and pop in `leave` stays balanced. Return `STOP` from `enter` or `leave` to end the walk there: no more `enter` or `leave` calls, not even `leave` on the node's ancestors, e.g. to find the first use of a rune. Return another node from `enter` to put it in this one's place: `walk` writes it to `parent[key]` (or `parent[key][index]`), then calls `enter` on it and visits its children instead, and `leave` gets the replacement. So `return node.expression` on each `TSAsExpression` strips `a as B as C` down to `a`, one layer per `enter`. The node you pass to `walk` can't be replaced. Checking `node.type` narrows `node`.
|
|
87
|
+
|
|
88
|
+
It works on any node the parsers return: a component's `Root`, a `Fragment`, an expression, a `parseModule` program, a `<style>`'s rules. It only reads the fields that hold child nodes, so it never descends into `loc`, comments or strings, and it throws on a node type it doesn't know. Two exceptions to source order: a component's sections are visited in scope order, `module`, `instance`, `fragment`, then `css`; and a template literal's `quasis` come before its `expressions`. `Root.options` isn't a node, so `<svelte:options>`'s attributes aren't visited; read them from `ast.options?.attributes`. acorn shares one `Identifier` between both names of `import { a }`, so it's visited once as `imported` and once as `local`, and likewise as `local` and `exported` in `export { a }`.
|
|
89
|
+
|
|
90
|
+
`visitorKeys` is the table `walk` reads: the fields of each node type that hold child nodes, in source order, e.g. `visitorKeys.IfBlock` is `["test", "consequent", "alternate"]`. Use it with another walker, or to write your own.
|
|
60
91
|
|
|
61
92
|
### Types
|
|
62
93
|
|
|
63
|
-
`AST` is svelte's `AST` namespace (`AST.Root`, `AST.RegularElement`, `AST.CSS.Rule`, ...), corrected to match what the parser returns: `name_loc` and a comment's `loc` are optional, `Root.instance`/`module` are absent rather than `null` when there's no such `<script>`, `Root.js` is declared, and every directive has `modifiers`. `ParseOptions`
|
|
94
|
+
`AST` is svelte's `AST` namespace (`AST.Root`, `AST.RegularElement`, `AST.CSS.Rule`, ...), corrected to match what the parser returns: `name_loc` and a comment's `loc` are optional, `Root.instance`/`module` are absent rather than `null` when there's no such `<script>`, `Root.js` is declared, and every directive has `modifiers`. `ParseOptions` and `ParseModuleOptions` are exported too.
|
|
64
95
|
|
|
65
96
|
The estree node types are exported as well (`Program`, `Node`, `Statement`, `Expression`, `Identifier`, ...), so you don't need `@types/estree`. They're estree's, plus what the parser adds: `start`/`end` on every node, and the TypeScript plugin's nodes (`TSInterfaceDeclaration`, `TSTypeAnnotation`, `TSTypeReference`, ...; `TSNode` is their union) and fields (`typeAnnotation`, `typeParameters`, `typeArguments`, `returnType`, `importKind`/`exportKind`, ...). The TypeScript nodes are in the `Statement`, `Declaration` and `Expression` unions, so checking `node.type` narrows to them. Where the grammar only allows a string, such as an import's `source`, the type is `StringLiteral`, a `Literal` whose `value` is a `string`.
|
|
66
97
|
|
|
@@ -78,28 +109,50 @@ for (const node of parseModule(source, { typescript: true }).body) {
|
|
|
78
109
|
**List the components a file renders**, e.g. to build a dependency graph:
|
|
79
110
|
|
|
80
111
|
```ts
|
|
81
|
-
import { parse } from "sveast";
|
|
112
|
+
import { parse, walk } from "sveast";
|
|
82
113
|
|
|
83
114
|
function componentsUsed(source: string): string[] {
|
|
84
115
|
const names = new Set<string>();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
typeof node.name === "string"
|
|
91
|
-
) {
|
|
92
|
-
names.add(node.name);
|
|
93
|
-
}
|
|
94
|
-
for (const child of Object.values(node)) {
|
|
95
|
-
if (typeof child === "object" && child !== null) visit(child);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
visit(parse(source, { css: false }).fragment);
|
|
116
|
+
walk(parse(source, { css: false }).fragment, {
|
|
117
|
+
enter(node) {
|
|
118
|
+
if (node.type === "Component") names.add(node.name);
|
|
119
|
+
},
|
|
120
|
+
});
|
|
99
121
|
return [...names]; // ["Button", "Modal.Root"]
|
|
100
122
|
}
|
|
101
123
|
```
|
|
102
124
|
|
|
125
|
+
**Find the classes a component's styles declare but its markup never uses**, e.g. to report dead CSS. A `class={...}` expression is reported as `dynamic` rather than guessed at:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { parse, walk } from "sveast";
|
|
129
|
+
|
|
130
|
+
function unusedClasses(source: string): { unused: string[]; dynamic: boolean } {
|
|
131
|
+
const ast = parse(source);
|
|
132
|
+
const declared = new Set<string>();
|
|
133
|
+
const used = new Set<string>();
|
|
134
|
+
let dynamic = false;
|
|
135
|
+
if (ast.css) {
|
|
136
|
+
walk(ast.css, {
|
|
137
|
+
enter(node) {
|
|
138
|
+
if (node.type === "ClassSelector") declared.add(node.name);
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
walk(ast.fragment, {
|
|
143
|
+
enter(node) {
|
|
144
|
+
if (node.type === "ClassDirective") used.add(node.name);
|
|
145
|
+
if (node.type !== "Attribute" || node.name !== "class") return;
|
|
146
|
+
for (const part of node.value === true ? [] : [node.value].flat()) {
|
|
147
|
+
if (part.type === "ExpressionTag") dynamic = true;
|
|
148
|
+
else for (const name of part.data.split(/\s+/)) if (name) used.add(name);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
return { unused: [...declared].filter((name) => !used.has(name)), dynamic };
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
103
156
|
**Read the props a runes component declares:**
|
|
104
157
|
|
|
105
158
|
```ts
|