wesl 0.7.26 → 0.7.28
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 +1 -1
- package/dist/index.d.ts +283 -147
- package/dist/index.js +1765 -1143
- package/package.json +2 -2
- package/src/AbstractElems.ts +264 -82
- package/src/ClickableError.ts +8 -1
- package/src/Linker.ts +63 -67
- package/src/LinkerUtil.ts +141 -7
- package/src/LowerAndEmit.ts +660 -304
- package/src/Mangler.ts +1 -1
- package/src/ModuleResolver.ts +15 -4
- package/src/ParseWESL.ts +21 -33
- package/src/SrcMap.ts +7 -19
- package/src/StandardTypes.ts +1 -1
- package/src/WeslDevice.ts +1 -0
- package/src/debug/ASTtoString.ts +92 -76
- package/src/index.ts +1 -1
- package/src/parse/AttachComments.ts +289 -0
- package/src/parse/ExpressionUtil.ts +3 -3
- package/src/parse/Keywords.ts +1 -1
- package/src/parse/ParseAttribute.ts +49 -71
- package/src/parse/ParseCall.ts +3 -4
- package/src/parse/ParseControlFlow.ts +100 -56
- package/src/parse/ParseDirective.ts +9 -8
- package/src/parse/ParseDoBlock.ts +63 -0
- package/src/parse/ParseExpression.ts +11 -10
- package/src/parse/ParseFn.ts +11 -33
- package/src/parse/ParseGlobalVar.ts +36 -27
- package/src/parse/ParseIdent.ts +4 -5
- package/src/parse/ParseLocalVar.ts +16 -12
- package/src/parse/ParseLoop.ts +76 -39
- package/src/parse/ParseModule.ts +65 -19
- package/src/parse/ParseSimpleStatement.ts +112 -66
- package/src/parse/ParseStatement.ts +40 -67
- package/src/parse/ParseStruct.ts +8 -22
- package/src/parse/ParseType.ts +10 -13
- package/src/parse/ParseUtil.ts +26 -12
- package/src/parse/ParseValueDeclaration.ts +18 -31
- package/src/parse/ParseWesl.ts +11 -14
- package/src/parse/ParsingContext.ts +11 -9
- package/src/parse/WeslStream.ts +138 -121
- package/src/parse/stream/RegexMatchers.ts +45 -0
- package/src/parse/stream/WeslLexer.ts +249 -0
- package/src/test/BevyLink.test.ts +18 -11
- package/src/test/ConditionalElif.test.ts +4 -2
- package/src/test/DeclCommentEmit.test.ts +122 -0
- package/src/test/DoBlock.test.ts +164 -0
- package/src/test/FilterValidElements.test.ts +4 -6
- package/src/test/Linker.test.ts +23 -7
- package/src/test/Mangling.test.ts +8 -4
- package/src/test/ParseComments.test.ts +234 -6
- package/src/test/ParseConditionsV2.test.ts +47 -175
- package/src/test/ParseElifV2.test.ts +12 -33
- package/src/test/ParseErrorV2.test.ts +0 -0
- package/src/test/ParseWeslV2.test.ts +247 -626
- package/src/test/StatementEmit.test.ts +143 -0
- package/src/test/StripWesl.ts +24 -3
- package/src/test/TestLink.ts +19 -3
- package/src/test/TestUtil.ts +18 -8
- package/src/test/Tokenizer.test.ts +96 -0
- package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +10 -42
- package/src/RawEmit.ts +0 -103
- package/src/Reflection.ts +0 -336
- package/src/TransformBindingStructs.ts +0 -320
- package/src/parse/ContentsHelpers.ts +0 -70
- package/src/parse/stream/CachingStream.ts +0 -48
- package/src/parse/stream/MatchersStream.ts +0 -85
package/src/Linker.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type ModuleResolver,
|
|
15
15
|
RecordResolver,
|
|
16
16
|
} from "./ModuleResolver.ts";
|
|
17
|
-
import type { WeslAST } from "./ParseWESL.ts";
|
|
17
|
+
import type { WeslAST, WeslExtensions } from "./ParseWESL.ts";
|
|
18
18
|
import type { Conditions, DeclIdent, SrcModule } from "./Scope.ts";
|
|
19
19
|
import { type SrcMap, SrcMapBuilder } from "./SrcMap.ts";
|
|
20
20
|
import { filterMap, mapValues } from "./Util.ts";
|
|
@@ -37,29 +37,17 @@ export interface LinkConfig {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export interface LinkParams {
|
|
40
|
-
/** Module resolver for lazy loading
|
|
41
|
-
* Replaces weslSrc for lazy module loading.
|
|
42
|
-
* If provided, weslSrc will be ignored. */
|
|
40
|
+
/** Module resolver for lazy loading. If provided, weslSrc is ignored. */
|
|
43
41
|
resolver?: ModuleResolver;
|
|
44
42
|
|
|
45
|
-
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* value is wesl src
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* Only accepts unix-style, relative filesystem paths that are valid WGSL identifiers
|
|
52
|
-
* - Unix-style: Slashes as separators.
|
|
53
|
-
* - Valid WGSL identifiers: No backslashes, no `..`, or other non-identifier symbols.
|
|
54
|
-
* - Relative paths: They have to be relative to the wesl root.
|
|
55
|
-
*/
|
|
43
|
+
/** Record of module sources keyed by module path (`package::foo::bar`)
|
|
44
|
+
* or relative file path (`./foo/bar.wesl`). Paths must be unix-style,
|
|
45
|
+
* relative to the wesl root, and valid WGSL identifiers. */
|
|
56
46
|
weslSrc?: Record<string, string>;
|
|
57
47
|
|
|
58
|
-
/** name
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* can be specified as file path (./main.wesl), a module path (package::main), or just a module name (main)
|
|
62
|
-
*/
|
|
48
|
+
/** Root module name: file path (`./main.wesl`), module path (`package::main`), or bare name (`main`).
|
|
49
|
+
* For apps, the root module contains entry points (`@compute`, `@vertex`, `@fragment`).
|
|
50
|
+
* For libraries, it defines the public API. */
|
|
63
51
|
rootModuleName?: string;
|
|
64
52
|
|
|
65
53
|
/** For debug logging. Will be prepended to file paths. */
|
|
@@ -89,6 +77,11 @@ export interface LinkParams {
|
|
|
89
77
|
|
|
90
78
|
/** function to construct globally unique wgsl identifiers */
|
|
91
79
|
mangler?: ManglerFn;
|
|
80
|
+
|
|
81
|
+
/** opt-in parsing of experimental, not-yet-spec'd syntax extensions.
|
|
82
|
+
* Only applied to the local source resolver built from `weslSrc`; callers
|
|
83
|
+
* that supply their own `resolver` must set it on that resolver. */
|
|
84
|
+
weslExtensions?: WeslExtensions;
|
|
92
85
|
}
|
|
93
86
|
|
|
94
87
|
/** Project config for web components and tools. */
|
|
@@ -100,7 +93,11 @@ export type WeslProject = Pick<
|
|
|
100
93
|
| "constants"
|
|
101
94
|
| "libs"
|
|
102
95
|
| "packageName"
|
|
103
|
-
|
|
96
|
+
| "weslExtensions"
|
|
97
|
+
> & {
|
|
98
|
+
/** Shader directory relative to project root (set by ?link from wesl.toml). */
|
|
99
|
+
shaderRoot?: string;
|
|
100
|
+
};
|
|
104
101
|
|
|
105
102
|
/** Context passed to virtual library generators. */
|
|
106
103
|
export interface VirtualLibContext {
|
|
@@ -127,26 +124,26 @@ export async function link(params: LinkParams): Promise<LinkedWesl> {
|
|
|
127
124
|
|
|
128
125
|
/** linker api for benchmarking */
|
|
129
126
|
export function _linkSync(params: LinkParams): SrcMap {
|
|
130
|
-
const { weslSrc, libs = [], packageName, debugWeslRoot } = params;
|
|
131
|
-
const {
|
|
132
|
-
|
|
133
|
-
const resolvers: ModuleResolver[] = [];
|
|
127
|
+
const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
|
|
128
|
+
const { weslExtensions } = params;
|
|
134
129
|
|
|
135
|
-
if (resolver) {
|
|
136
|
-
resolvers.push(resolver);
|
|
137
|
-
} else if (weslSrc) {
|
|
138
|
-
resolvers.push(new RecordResolver(weslSrc, { packageName, debugWeslRoot }));
|
|
139
|
-
} else {
|
|
130
|
+
if (!resolver && !weslSrc) {
|
|
140
131
|
throw new Error("Either resolver or weslSrc must be provided");
|
|
141
132
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
133
|
+
const primaryResolver =
|
|
134
|
+
resolver ??
|
|
135
|
+
new RecordResolver(weslSrc!, {
|
|
136
|
+
packageName,
|
|
137
|
+
debugWeslRoot,
|
|
138
|
+
weslExtensions,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
|
|
142
|
+
const allResolvers = [primaryResolver, ...libResolvers];
|
|
148
143
|
const finalResolver =
|
|
149
|
-
|
|
144
|
+
allResolvers.length === 1
|
|
145
|
+
? allResolvers[0]
|
|
146
|
+
: new CompositeResolver(allResolvers);
|
|
150
147
|
|
|
151
148
|
return linkRegistry({ ...params, resolver: finalResolver });
|
|
152
149
|
}
|
|
@@ -200,11 +197,8 @@ export interface LinkRegistryParams
|
|
|
200
197
|
* that share some sources.)
|
|
201
198
|
*/
|
|
202
199
|
export function linkRegistry(params: LinkRegistryParams): SrcMap {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
newDecls,
|
|
206
|
-
newStatements,
|
|
207
|
-
} = bindAndTransform(params);
|
|
200
|
+
const bound = bindAndTransform(params);
|
|
201
|
+
const { transformedAst: ast, newDecls, newStatements } = bound;
|
|
208
202
|
const builders = emitWgsl(
|
|
209
203
|
ast.moduleElem,
|
|
210
204
|
ast.srcModule,
|
|
@@ -230,7 +224,6 @@ export function bindAndTransform(
|
|
|
230
224
|
|
|
231
225
|
const modulePath = normalizeModuleName(rootModuleName);
|
|
232
226
|
const rootAst = getRootModule(resolver, modulePath, rootModuleName);
|
|
233
|
-
|
|
234
227
|
const virtuals = setupVirtualLibs(params.virtualLibs, constants);
|
|
235
228
|
|
|
236
229
|
const bound = bindIdents({
|
|
@@ -291,14 +284,14 @@ function setupVirtualLibs(
|
|
|
291
284
|
return libs && mapValues(libs, fn => ({ fn }));
|
|
292
285
|
}
|
|
293
286
|
|
|
287
|
+
/** Run registered transform plugins over the bound AST. */
|
|
294
288
|
function applyTransformPlugins(
|
|
295
289
|
rootModule: WeslAST,
|
|
296
290
|
globalNames: Set<string>,
|
|
297
291
|
config?: LinkConfig,
|
|
298
292
|
): TransformedAST {
|
|
299
|
-
const { moduleElem, srcModule } = rootModule;
|
|
300
|
-
|
|
301
293
|
// for now only transform the root module
|
|
294
|
+
const { moduleElem, srcModule } = rootModule;
|
|
302
295
|
const startAst = { moduleElem, srcModule, globalNames, notableElems: {} };
|
|
303
296
|
const plugins = config?.plugins ?? [];
|
|
304
297
|
const transforms = filterMap(plugins, plugin => plugin.transform);
|
|
@@ -313,12 +306,11 @@ function emitWgsl(
|
|
|
313
306
|
newStatements: EmittableElem[],
|
|
314
307
|
conditions: Conditions = {},
|
|
315
308
|
): SrcMapBuilder[] {
|
|
316
|
-
const prologueBuilders = newStatements.map(s =>
|
|
309
|
+
const prologueBuilders = newStatements.map(s =>
|
|
310
|
+
emitElem(s.srcModule, s.elem, conditions, { addNl: true }),
|
|
311
|
+
);
|
|
317
312
|
|
|
318
|
-
const rootBuilder =
|
|
319
|
-
text: srcModule.src,
|
|
320
|
-
path: srcModule.debugFilePath,
|
|
321
|
-
});
|
|
313
|
+
const rootBuilder = builderFromModule(srcModule);
|
|
322
314
|
lowerAndEmit({
|
|
323
315
|
srcBuilder: rootBuilder,
|
|
324
316
|
rootElems: [rootModuleElem],
|
|
@@ -326,36 +318,40 @@ function emitWgsl(
|
|
|
326
318
|
extracting: false,
|
|
327
319
|
});
|
|
328
320
|
|
|
329
|
-
const declBuilders = newDecls.map(decl =>
|
|
321
|
+
const declBuilders = newDecls.map(decl =>
|
|
322
|
+
emitElem(decl.srcModule, decl.declElem!, conditions, {
|
|
323
|
+
skipConditionalFiltering: true,
|
|
324
|
+
}),
|
|
325
|
+
);
|
|
330
326
|
|
|
331
327
|
return [...prologueBuilders, rootBuilder, ...declBuilders];
|
|
332
328
|
}
|
|
333
329
|
|
|
334
|
-
|
|
335
|
-
|
|
330
|
+
/** Emit a single element (prologue statement or imported declaration) into a SrcMapBuilder. */
|
|
331
|
+
function emitElem(
|
|
332
|
+
srcModule: SrcModule,
|
|
333
|
+
elem: AbstractElem,
|
|
336
334
|
conditions: Conditions,
|
|
335
|
+
opts: { addNl?: boolean; skipConditionalFiltering?: boolean } = {},
|
|
337
336
|
): SrcMapBuilder {
|
|
338
|
-
const
|
|
339
|
-
const { src: text, debugFilePath: path } = srcModule;
|
|
340
|
-
const builder = new SrcMapBuilder({ text, path });
|
|
341
|
-
lowerAndEmit({ srcBuilder: builder, rootElems: [elem], conditions });
|
|
342
|
-
builder.addNl();
|
|
343
|
-
return builder;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/** Skip conditional filtering because findValidRootDecls already validated these declarations */
|
|
347
|
-
function emitDecl(decl: DeclIdent, conditions: Conditions): SrcMapBuilder {
|
|
348
|
-
const { src: text, debugFilePath: path } = decl.srcModule;
|
|
349
|
-
const builder = new SrcMapBuilder({ text, path });
|
|
337
|
+
const builder = builderFromModule(srcModule);
|
|
350
338
|
lowerAndEmit({
|
|
351
339
|
srcBuilder: builder,
|
|
352
|
-
rootElems: [
|
|
340
|
+
rootElems: [elem],
|
|
353
341
|
conditions,
|
|
354
|
-
skipConditionalFiltering:
|
|
342
|
+
skipConditionalFiltering: opts.skipConditionalFiltering,
|
|
355
343
|
});
|
|
344
|
+
if (opts.addNl) builder.addNl();
|
|
356
345
|
return builder;
|
|
357
346
|
}
|
|
358
347
|
|
|
348
|
+
function builderFromModule(srcModule: SrcModule): SrcMapBuilder {
|
|
349
|
+
return new SrcMapBuilder({
|
|
350
|
+
text: srcModule.src,
|
|
351
|
+
path: srcModule.debugFilePath,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
359
355
|
/*
|
|
360
356
|
|
|
361
357
|
LATER
|
package/src/LinkerUtil.ts
CHANGED
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AbstractElem,
|
|
3
|
-
ContainerElem,
|
|
4
3
|
DeclIdentElem,
|
|
4
|
+
ExpressionElem,
|
|
5
|
+
HasAttributes,
|
|
6
|
+
ModuleElem,
|
|
5
7
|
RefIdentElem,
|
|
6
8
|
} from "./AbstractElems.ts";
|
|
9
|
+
import { assertUnreachableSilent } from "./Assertions.ts";
|
|
7
10
|
import { srcLog } from "./Logging.ts";
|
|
8
11
|
|
|
12
|
+
/** A module's top-level declarations of a given kind, narrowed to that elem type. */
|
|
13
|
+
export function declsOfKind<K extends AbstractElem["kind"]>(
|
|
14
|
+
moduleElem: ModuleElem,
|
|
15
|
+
kind: K,
|
|
16
|
+
): Extract<AbstractElem, { kind: K }>[] {
|
|
17
|
+
return moduleElem.decls.filter(
|
|
18
|
+
(e): e is Extract<AbstractElem, { kind: K }> => e.kind === kind,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Visit an elem and all its descendants, parent before children (pre-order). */
|
|
9
23
|
export function visitAst(
|
|
10
24
|
elem: AbstractElem,
|
|
11
25
|
visitor: (elem: AbstractElem) => void,
|
|
12
26
|
) {
|
|
13
27
|
visitor(elem);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
for (const child of childElems(elem)) visitAst(child, visitor);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Child elems of any AST node: the typed structural fields (attributes plus
|
|
33
|
+
* body / condition / decls / ...) in source order. Returns [] for leaf elems.
|
|
34
|
+
*/
|
|
35
|
+
export function childElems(elem: AbstractElem): readonly AbstractElem[] {
|
|
36
|
+
const attributes = (elem as HasAttributes).attributes ?? [];
|
|
37
|
+
return [...attributes, ...structuralFields(elem)];
|
|
20
38
|
}
|
|
21
39
|
|
|
22
40
|
export function identElemLog(
|
|
@@ -29,3 +47,119 @@ export function identElemLog(
|
|
|
29
47
|
...messages,
|
|
30
48
|
);
|
|
31
49
|
}
|
|
50
|
+
|
|
51
|
+
/** The child elems held in a node's typed fields, in source order (attributes
|
|
52
|
+
* are added separately by {@link childElems}). Empty for leaf kinds. The
|
|
53
|
+
* switch is exhaustive: a new elem kind without a case fails to compile in the
|
|
54
|
+
* default, so children can't be silently dropped. */
|
|
55
|
+
function structuralFields(elem: AbstractElem): AbstractElem[] {
|
|
56
|
+
switch (elem.kind) {
|
|
57
|
+
case "module":
|
|
58
|
+
return elem.decls;
|
|
59
|
+
case "var":
|
|
60
|
+
case "gvar":
|
|
61
|
+
return [
|
|
62
|
+
...(elem.template ?? []),
|
|
63
|
+
elem.name,
|
|
64
|
+
...(elem.init ? [elem.init] : []),
|
|
65
|
+
];
|
|
66
|
+
case "let":
|
|
67
|
+
case "const":
|
|
68
|
+
case "override":
|
|
69
|
+
return [elem.name, ...(elem.init ? [elem.init] : [])];
|
|
70
|
+
case "alias":
|
|
71
|
+
return [elem.name, elem.typeRef];
|
|
72
|
+
case "assert":
|
|
73
|
+
return [elem.expression];
|
|
74
|
+
case "struct":
|
|
75
|
+
return [elem.name, ...elem.members];
|
|
76
|
+
case "member":
|
|
77
|
+
return [elem.name, elem.typeRef];
|
|
78
|
+
case "type":
|
|
79
|
+
return [elem.name.refIdentElem, ...(elem.templateParams ?? [])];
|
|
80
|
+
case "expression":
|
|
81
|
+
return [elem.expression];
|
|
82
|
+
case "binary-expression":
|
|
83
|
+
return [elem.left, elem.right];
|
|
84
|
+
case "unary-expression":
|
|
85
|
+
case "parenthesized-expression":
|
|
86
|
+
return [elem.expression];
|
|
87
|
+
case "component-expression":
|
|
88
|
+
return [elem.base, elem.access];
|
|
89
|
+
case "component-member-expression":
|
|
90
|
+
return [elem.base, elem.access];
|
|
91
|
+
case "call-expression":
|
|
92
|
+
return [elem.function, ...(elem.templateArgs ?? []), ...elem.arguments];
|
|
93
|
+
case "param":
|
|
94
|
+
return [elem.name];
|
|
95
|
+
case "typeDecl":
|
|
96
|
+
return elem.typeRef ? [elem.decl, elem.typeRef] : [elem.decl];
|
|
97
|
+
case "fn":
|
|
98
|
+
return [
|
|
99
|
+
elem.name,
|
|
100
|
+
...elem.params,
|
|
101
|
+
...(elem.returnType ? [elem.returnType] : []),
|
|
102
|
+
elem.body,
|
|
103
|
+
];
|
|
104
|
+
case "block":
|
|
105
|
+
return elem.body;
|
|
106
|
+
case "if":
|
|
107
|
+
return [elem.condition, elem.body, ...(elem.else ? [elem.else] : [])];
|
|
108
|
+
case "for":
|
|
109
|
+
return [elem.init, elem.condition, elem.update, elem.body].filter(
|
|
110
|
+
isDefined,
|
|
111
|
+
);
|
|
112
|
+
case "while":
|
|
113
|
+
return [elem.condition, elem.body];
|
|
114
|
+
case "loop":
|
|
115
|
+
case "continuing":
|
|
116
|
+
return [elem.body];
|
|
117
|
+
case "switch":
|
|
118
|
+
return [elem.selector, ...(elem.bodyAttributes ?? []), ...elem.clauses];
|
|
119
|
+
case "switch-clause":
|
|
120
|
+
return [...exprSelectors(elem.selectors), elem.body];
|
|
121
|
+
case "return":
|
|
122
|
+
return elem.value ? [elem.value] : [];
|
|
123
|
+
case "break":
|
|
124
|
+
return elem.condition ? [elem.condition] : [];
|
|
125
|
+
case "assign":
|
|
126
|
+
return elem.lhs.kind === "phony" ? [elem.rhs] : [elem.lhs, elem.rhs];
|
|
127
|
+
case "increment":
|
|
128
|
+
case "decrement":
|
|
129
|
+
return [elem.target];
|
|
130
|
+
case "call":
|
|
131
|
+
return [elem.call];
|
|
132
|
+
case "continue":
|
|
133
|
+
case "discard":
|
|
134
|
+
case "empty":
|
|
135
|
+
return [];
|
|
136
|
+
case "do":
|
|
137
|
+
return [elem.name, ...elem.params, elem.body];
|
|
138
|
+
// import/directive hold no child elems in typed fields (their payload isn't
|
|
139
|
+
// an AbstractElem), but they carry attributes that childElems prepends.
|
|
140
|
+
case "import":
|
|
141
|
+
case "directive":
|
|
142
|
+
// leaf kinds: no child elems and no attributes.
|
|
143
|
+
case "attribute":
|
|
144
|
+
case "name":
|
|
145
|
+
case "literal":
|
|
146
|
+
case "ref":
|
|
147
|
+
case "decl":
|
|
148
|
+
case "synthetic":
|
|
149
|
+
return [];
|
|
150
|
+
default:
|
|
151
|
+
assertUnreachableSilent(elem);
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isDefined<T>(value: T | undefined): value is T {
|
|
157
|
+
return value !== undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Drop the `"default"` sentinel, keeping only real case-selector expressions. */
|
|
161
|
+
function exprSelectors(
|
|
162
|
+
selectors: (ExpressionElem | "default")[],
|
|
163
|
+
): ExpressionElem[] {
|
|
164
|
+
return selectors.filter((s): s is ExpressionElem => s !== "default");
|
|
165
|
+
}
|