@invariant-ui/cli 0.0.0-stage → 0.1.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/LICENSE +105 -0
- package/README.md +248 -2
- package/dist/agent-kit/apply.js +232 -0
- package/dist/agent-kit/guidance.js +236 -0
- package/dist/agent-kit/index.js +5 -0
- package/dist/agent-kit/integrations.js +121 -0
- package/dist/agent-kit/plugin.js +115 -0
- package/dist/artifact-generator/index.js +75 -0
- package/dist/browser-verify/driver.js +2 -0
- package/dist/browser-verify/index.js +4 -0
- package/dist/browser-verify/playwright.js +82 -0
- package/dist/browser-verify/runner.js +175 -0
- package/dist/browser-verify/spec.js +103 -0
- package/dist/cli/args.js +77 -0
- package/dist/cli/baseline.js +64 -0
- package/dist/cli/cli.js +121 -0
- package/dist/cli/commands-browser.js +104 -0
- package/dist/cli/commands-discover.js +312 -0
- package/dist/cli/commands-enforcement.js +35 -0
- package/dist/cli/commands-governance.js +179 -0
- package/dist/cli/commands-hook.js +269 -0
- package/dist/cli/commands-patterns.js +324 -0
- package/dist/cli/commands-setup.js +485 -0
- package/dist/cli/commands-validate.js +157 -0
- package/dist/cli/commands.js +517 -0
- package/dist/cli/config.js +197 -0
- package/dist/cli/diff.js +66 -0
- package/dist/cli/enforcement-log.js +83 -0
- package/dist/cli/enforcement-report.js +96 -0
- package/dist/cli/errors.js +10 -0
- package/dist/cli/format.js +22 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/io.js +2 -0
- package/dist/cli/main.js +31 -0
- package/dist/cli/pipeline.js +262 -0
- package/dist/cli/publish.js +77 -0
- package/dist/cli/report.js +127 -0
- package/dist/cli/starter-rules.js +57 -0
- package/dist/cli/store.js +60 -0
- package/dist/cli/version.js +5 -0
- package/dist/cli/watch.js +68 -0
- package/dist/cli/workflow.js +52 -0
- package/dist/compiler-angular/adapter.js +115 -0
- package/dist/compiler-angular/analyze.js +222 -0
- package/dist/compiler-angular/classes.js +446 -0
- package/dist/compiler-angular/declared.js +285 -0
- package/dist/compiler-angular/emit.js +183 -0
- package/dist/compiler-angular/index.js +4 -0
- package/dist/compiler-angular/template.js +142 -0
- package/dist/compiler-core/adapter.js +2 -0
- package/dist/compiler-core/adapters/inventory.js +312 -0
- package/dist/compiler-core/compile.js +148 -0
- package/dist/compiler-core/component-facts.js +123 -0
- package/dist/compiler-core/discovery/docs.js +66 -0
- package/dist/compiler-core/discovery/entries.js +270 -0
- package/dist/compiler-core/discovery/exports.js +207 -0
- package/dist/compiler-core/discovery/graph.js +59 -0
- package/dist/compiler-core/discovery/index.js +103 -0
- package/dist/compiler-core/discovery/packages.js +148 -0
- package/dist/compiler-core/discovery/schema.js +144 -0
- package/dist/compiler-core/discovery/storybook.js +232 -0
- package/dist/compiler-core/discovery/support.js +82 -0
- package/dist/compiler-core/discovery/tokens.js +186 -0
- package/dist/compiler-core/discovery/tooling.js +146 -0
- package/dist/compiler-core/fs.js +89 -0
- package/dist/compiler-core/glob.js +3 -0
- package/dist/compiler-core/index.js +16 -0
- package/dist/compiler-core/ir-builder.js +68 -0
- package/dist/compiler-core/json-schema.js +11 -0
- package/dist/compiler-core/testing/index.js +81 -0
- package/dist/compiler-core/ts/ast.js +166 -0
- package/dist/compiler-core/ts/members.js +163 -0
- package/dist/compiler-core/ts/program.js +81 -0
- package/dist/compiler-core/ts/static-eval.js +145 -0
- package/dist/compiler-core/ts/tsconfig-paths.js +60 -0
- package/dist/compiler-core/walk.js +46 -0
- package/dist/compiler-ir/index.js +4 -0
- package/dist/compiler-ir/ir.js +67 -0
- package/dist/compiler-ir/json-schema.js +6 -0
- package/dist/compiler-ir/validate.js +116 -0
- package/dist/compiler-react/adapter.js +147 -0
- package/dist/compiler-react/analyze.js +199 -0
- package/dist/compiler-react/ast.js +105 -0
- package/dist/compiler-react/behavior.js +75 -0
- package/dist/compiler-react/detect.js +558 -0
- package/dist/compiler-react/emit.js +188 -0
- package/dist/compiler-react/index.js +2 -0
- package/dist/compiler-react/program.js +58 -0
- package/dist/compiler-react/props.js +157 -0
- package/dist/compiler-react/styling.js +138 -0
- package/dist/compiler-react/usage.js +116 -0
- package/dist/compiler-react/variants.js +111 -0
- package/dist/compiler-storybook/adapter.js +253 -0
- package/dist/compiler-storybook/csf.js +521 -0
- package/dist/compiler-storybook/emit.js +202 -0
- package/dist/compiler-storybook/index-json.js +41 -0
- package/dist/compiler-storybook/index.js +5 -0
- package/dist/compiler-storybook/mdx.js +120 -0
- package/dist/compiler-storybook/static-eval.js +145 -0
- package/dist/compiler-tokens/adapter.js +158 -0
- package/dist/compiler-tokens/css.js +59 -0
- package/dist/compiler-tokens/index.js +5 -0
- package/dist/compiler-tokens/json-lines.js +106 -0
- package/dist/compiler-tokens/json-tokens.js +52 -0
- package/dist/compiler-vue/adapter.js +107 -0
- package/dist/compiler-vue/analyze.js +344 -0
- package/dist/compiler-vue/declared.js +343 -0
- package/dist/compiler-vue/emit.js +129 -0
- package/dist/compiler-vue/index.js +5 -0
- package/dist/compiler-vue/script.js +472 -0
- package/dist/compiler-vue/sfc.js +44 -0
- package/dist/compiler-vue/template.js +73 -0
- package/dist/contract/archetype-checks.js +153 -0
- package/dist/contract/components.js +102 -0
- package/dist/contract/conflicts.js +39 -0
- package/dist/contract/contract.js +46 -0
- package/dist/contract/evidence.js +84 -0
- package/dist/contract/fact-path.js +39 -0
- package/dist/contract/facts.js +62 -0
- package/dist/contract/index.js +16 -0
- package/dist/contract/integrity.js +21 -0
- package/dist/contract/json-schema.js +11 -0
- package/dist/contract/knowledge.js +95 -0
- package/dist/contract/pattern-checks.js +254 -0
- package/dist/contract/patterns.js +225 -0
- package/dist/contract/primitives.js +81 -0
- package/dist/contract/sources.js +30 -0
- package/dist/contract/tokens.js +41 -0
- package/dist/contract/validate.js +253 -0
- package/dist/contract-query/archetype.js +188 -0
- package/dist/contract-query/index.js +6 -0
- package/dist/contract-query/patterns.js +298 -0
- package/dist/contract-query/scan.js +164 -0
- package/dist/contract-query/search.js +132 -0
- package/dist/contract-query/system.js +148 -0
- package/dist/contract-query/validate-code.js +239 -0
- package/dist/contract-query/validate-usage.js +150 -0
- package/dist/contract-query/views.js +288 -0
- package/dist/governance/decisions.js +229 -0
- package/dist/governance/index.js +5 -0
- package/dist/governance/patterns-file.js +156 -0
- package/dist/governance/patterns.js +510 -0
- package/dist/governance/policies.js +148 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/loader.js +70 -0
- package/dist/mcp/protocol.js +27 -0
- package/dist/mcp/resources.js +138 -0
- package/dist/mcp/server.js +177 -0
- package/dist/mcp/stdio.js +28 -0
- package/dist/mcp/tools.js +267 -0
- package/dist/reconciliation/code.js +62 -0
- package/dist/reconciliation/components.js +401 -0
- package/dist/reconciliation/conflicts.js +126 -0
- package/dist/reconciliation/facts.js +41 -0
- package/dist/reconciliation/index.js +7 -0
- package/dist/reconciliation/reconcile.js +134 -0
- package/dist/reconciliation/resolve.js +158 -0
- package/dist/reconciliation/review.js +81 -0
- package/dist/reconciliation/storybook.js +370 -0
- package/dist/reconciliation/tokens.js +179 -0
- package/dist/reconciliation/util.js +55 -0
- package/dist/semantic-engine/additive.js +51 -0
- package/dist/semantic-engine/compounds.js +91 -0
- package/dist/semantic-engine/documents.js +157 -0
- package/dist/semantic-engine/enrich.js +42 -0
- package/dist/semantic-engine/index.js +7 -0
- package/dist/semantic-engine/infer.js +38 -0
- package/dist/semantic-engine/tokens.js +120 -0
- package/dist/shared/canonical.js +65 -0
- package/dist/shared/diagnostic.js +78 -0
- package/dist/shared/glob.js +98 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/json.js +2 -0
- package/dist/validation/card.js +50 -0
- package/dist/validation/discovery/arrangements.js +162 -0
- package/dist/validation/discovery/discover.js +70 -0
- package/dist/validation/discovery/index.js +8 -0
- package/dist/validation/discovery/model.js +127 -0
- package/dist/validation/discovery/promote.js +24 -0
- package/dist/validation/discovery/reviews.js +63 -0
- package/dist/validation/discovery/shapes.js +47 -0
- package/dist/validation/discovery/similar-files.js +146 -0
- package/dist/validation/discovery/util.js +47 -0
- package/dist/validation/index.js +11 -0
- package/dist/validation/interaction-facts.js +341 -0
- package/dist/validation/interaction-workflows.js +633 -0
- package/dist/validation/literals.js +251 -0
- package/dist/validation/pattern-rules.js +608 -0
- package/dist/validation/policy.js +241 -0
- package/dist/validation/report.js +163 -0
- package/dist/validation/rules.js +269 -0
- package/dist/validation/scan.js +295 -0
- package/dist/validation/text.js +34 -0
- package/dist/validation/validate-code.js +513 -0
- package/dist/validation/validate-usage.js +196 -0
- package/package.json +38 -4
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { collapse, declarationOf, externalBases, hasModifier, isProjectNode, lineOf, relativeFileOf, resolveSymbol, typeReferenceInfo } from "../compiler-core/index.js";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { typeInfoOf } from "./classes.js";
|
|
4
|
+
import { slotName } from "./template.js";
|
|
5
|
+
const DECLARATIONS = {
|
|
6
|
+
ɵɵComponentDeclaration: "component",
|
|
7
|
+
ɵɵDirectiveDeclaration: "directive",
|
|
8
|
+
ɵɵNgModuleDeclaration: "module"
|
|
9
|
+
};
|
|
10
|
+
const MARKERS = new Set(["ɵcmp", "ɵdir", "ɵmod"]);
|
|
11
|
+
/** Positions of `ɵɵComponentDeclaration<T, Selector, ExportAs, Inputs, Outputs, Queries, NgContent, Standalone, HostDirectives>`. */
|
|
12
|
+
const SELECTOR = 1;
|
|
13
|
+
const EXPORT_AS = 2;
|
|
14
|
+
const INPUTS = 3;
|
|
15
|
+
const OUTPUTS = 4;
|
|
16
|
+
const NG_CONTENT = 6;
|
|
17
|
+
const STANDALONE = 7;
|
|
18
|
+
const HOST_DIRECTIVES = 8;
|
|
19
|
+
const SIGNAL_INPUTS = {
|
|
20
|
+
InputSignal: "signal-input",
|
|
21
|
+
InputSignalWithTransform: "signal-input",
|
|
22
|
+
ModelSignal: "model"
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Reads a class of a declaration file the way the Angular compiler wrote it. A published library has no
|
|
26
|
+
* decorators left: ng-packagr emits `static ɵcmp: ɵɵComponentDeclaration<Button, "ui-button", never, { "variant":
|
|
27
|
+
* { "alias": "variant"; "required": false } }, { "clicked": "clicked" }, never, ["*"], true>` for a component
|
|
28
|
+
* (`ɵdir` for a directive, `ɵmod` for an NgModule). That type is the compiled metadata: selector, public input and
|
|
29
|
+
* output names, projection slots and standalone. Property types come from the class members. Nothing is executed.
|
|
30
|
+
*/
|
|
31
|
+
export function readDeclaredClass(analysis, node, seen = new Set()) {
|
|
32
|
+
if (!node.name || !node.getSourceFile().isDeclarationFile || seen.has(node))
|
|
33
|
+
return undefined;
|
|
34
|
+
seen.add(node);
|
|
35
|
+
const file = relativeFileOf(analysis, node);
|
|
36
|
+
const marker = node.members.find((m) => ts.isPropertyDeclaration(m) &&
|
|
37
|
+
hasModifier(m, ts.SyntaxKind.StaticKeyword) &&
|
|
38
|
+
ts.isIdentifier(m.name) &&
|
|
39
|
+
MARKERS.has(m.name.text));
|
|
40
|
+
const ref = marker?.type ? typeReferenceInfo(marker.type) : undefined;
|
|
41
|
+
const kind = ref ? DECLARATIONS[ref.name] : undefined;
|
|
42
|
+
if (!file || !ref || !kind)
|
|
43
|
+
return undefined;
|
|
44
|
+
const out = {
|
|
45
|
+
node,
|
|
46
|
+
name: node.name.text,
|
|
47
|
+
file,
|
|
48
|
+
line: lineOf(node),
|
|
49
|
+
kind,
|
|
50
|
+
imports: [],
|
|
51
|
+
declarations: [],
|
|
52
|
+
exports: [],
|
|
53
|
+
styles: [],
|
|
54
|
+
styleUrls: [],
|
|
55
|
+
host: {},
|
|
56
|
+
hostDirectives: [],
|
|
57
|
+
metadataInputs: [],
|
|
58
|
+
metadataOutputs: [],
|
|
59
|
+
inputs: [],
|
|
60
|
+
outputs: [],
|
|
61
|
+
hostListeners: [],
|
|
62
|
+
hostBindings: [],
|
|
63
|
+
queries: [],
|
|
64
|
+
inheritsFrom: [],
|
|
65
|
+
notes: []
|
|
66
|
+
};
|
|
67
|
+
const arg = (i) => ref.typeArguments[i];
|
|
68
|
+
if (kind === "module") {
|
|
69
|
+
out.declarations = namesOf(analysis, arg(1));
|
|
70
|
+
out.imports = namesOf(analysis, arg(2));
|
|
71
|
+
out.exports = namesOf(analysis, arg(3));
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
const selector = stringOf(arg(SELECTOR));
|
|
75
|
+
if (selector !== undefined)
|
|
76
|
+
out.selector = selector;
|
|
77
|
+
const standalone = arg(STANDALONE)?.getText();
|
|
78
|
+
if (standalone === "true" || standalone === "false")
|
|
79
|
+
out.standalone = standalone === "true";
|
|
80
|
+
const exportAs = tupleOf(arg(EXPORT_AS))[0];
|
|
81
|
+
if (exportAs !== undefined)
|
|
82
|
+
out.exportAs = exportAs;
|
|
83
|
+
out.hostDirectives = hostDirectivesOf(arg(HOST_DIRECTIVES));
|
|
84
|
+
const used = new Set();
|
|
85
|
+
out.projection = tupleOf(arg(NG_CONTENT)).map((select) => {
|
|
86
|
+
let name = select === "*" ? "default" : slotName(select);
|
|
87
|
+
for (let n = 2; used.has(name); n++)
|
|
88
|
+
name = `${select === "*" ? "default" : slotName(select)}_${n}`;
|
|
89
|
+
used.add(name);
|
|
90
|
+
return { name, ...(select === "*" ? {} : { select }) };
|
|
91
|
+
});
|
|
92
|
+
const members = membersOf(node);
|
|
93
|
+
for (const [property, spec] of inputMap(arg(INPUTS))) {
|
|
94
|
+
out.inputs.push(inputOf(analysis, members, property, spec, node));
|
|
95
|
+
}
|
|
96
|
+
for (const [property, alias] of outputMap(arg(OUTPUTS))) {
|
|
97
|
+
out.outputs.push(outputOf(analysis, members, out.inputs, property, alias, node));
|
|
98
|
+
}
|
|
99
|
+
const base = node.heritageClauses?.find((h) => h.token === ts.SyntaxKind.ExtendsKeyword)?.types[0];
|
|
100
|
+
if (base) {
|
|
101
|
+
const target = declarationOf(resolveSymbol(analysis.checker, analysis.checker.getSymbolAtLocation(base.expression)));
|
|
102
|
+
const parent = target && ts.isClassDeclaration(target) && isProjectNode(analysis, target)
|
|
103
|
+
? readDeclaredClass(analysis, target, seen)
|
|
104
|
+
: undefined;
|
|
105
|
+
if (parent) {
|
|
106
|
+
for (const i of parent.inputs)
|
|
107
|
+
if (!out.inputs.some((x) => x.name === i.name))
|
|
108
|
+
out.inputs.push({ ...i, inheritedFrom: i.inheritedFrom ?? parent.name });
|
|
109
|
+
for (const o of parent.outputs)
|
|
110
|
+
if (!out.outputs.some((x) => x.name === o.name))
|
|
111
|
+
out.outputs.push({ ...o, inheritedFrom: o.inheritedFrom ?? parent.name });
|
|
112
|
+
out.inheritsFrom.push(...parent.inheritsFrom);
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
out.inheritsFrom.push(...externalBases(analysis, base));
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
function unparen(type) {
|
|
120
|
+
return ts.isParenthesizedTypeNode(type) ? unparen(type.type) : type;
|
|
121
|
+
}
|
|
122
|
+
/** `"ui-button"`; `never` and anything else is undefined. */
|
|
123
|
+
function stringOf(type) {
|
|
124
|
+
const t = type ? unparen(type) : undefined;
|
|
125
|
+
return t && ts.isLiteralTypeNode(t) && ts.isStringLiteralLike(t.literal) ? t.literal.text : undefined;
|
|
126
|
+
}
|
|
127
|
+
/** `["*", "[header]"]`; `never` is the empty list. */
|
|
128
|
+
function tupleOf(type) {
|
|
129
|
+
const t = type ? unparen(type) : undefined;
|
|
130
|
+
return t && ts.isTupleTypeNode(t) ? t.elements.flatMap((e) => stringOf(e) ?? []) : [];
|
|
131
|
+
}
|
|
132
|
+
function keyOf(member) {
|
|
133
|
+
return member.name && (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name))
|
|
134
|
+
? member.name.text
|
|
135
|
+
: undefined;
|
|
136
|
+
}
|
|
137
|
+
/** `{ "variant": { "alias": "variant"; "required": false; "isSignal": true } }`, or the older `{ "variant": "alias" }`. */
|
|
138
|
+
function inputMap(type) {
|
|
139
|
+
const t = type ? unparen(type) : undefined;
|
|
140
|
+
if (!t || !ts.isTypeLiteralNode(t))
|
|
141
|
+
return [];
|
|
142
|
+
const out = [];
|
|
143
|
+
for (const member of t.members) {
|
|
144
|
+
const property = keyOf(member);
|
|
145
|
+
if (property === undefined || !ts.isPropertySignature(member) || !member.type)
|
|
146
|
+
continue;
|
|
147
|
+
const value = unparen(member.type);
|
|
148
|
+
const alias = stringOf(value);
|
|
149
|
+
if (alias !== undefined) {
|
|
150
|
+
out.push([property, { alias, required: false, isSignal: false }]);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!ts.isTypeLiteralNode(value))
|
|
154
|
+
continue;
|
|
155
|
+
const field = (key) => value.members.find((m) => ts.isPropertySignature(m) && keyOf(m) === key)
|
|
156
|
+
?.type;
|
|
157
|
+
out.push([
|
|
158
|
+
property,
|
|
159
|
+
{
|
|
160
|
+
alias: stringOf(field("alias")) ?? property,
|
|
161
|
+
required: field("required")?.getText() === "true",
|
|
162
|
+
isSignal: field("isSignal")?.getText() === "true"
|
|
163
|
+
}
|
|
164
|
+
]);
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
/** `{ "clicked": "clicked" }`: class property to public event name. */
|
|
169
|
+
function outputMap(type) {
|
|
170
|
+
const t = type ? unparen(type) : undefined;
|
|
171
|
+
if (!t || !ts.isTypeLiteralNode(t))
|
|
172
|
+
return [];
|
|
173
|
+
return t.members.flatMap((member) => {
|
|
174
|
+
const property = keyOf(member);
|
|
175
|
+
const alias = ts.isPropertySignature(member) ? stringOf(member.type) : undefined;
|
|
176
|
+
return property !== undefined && alias !== undefined ? [[property, alias]] : [];
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function hostDirectivesOf(type) {
|
|
180
|
+
const t = type ? unparen(type) : undefined;
|
|
181
|
+
if (!t || !ts.isTupleTypeNode(t))
|
|
182
|
+
return [];
|
|
183
|
+
return t.elements.flatMap((e) => {
|
|
184
|
+
const literal = ts.isTypeLiteralNode(unparen(e)) ? unparen(e) : undefined;
|
|
185
|
+
const directive = literal?.members.find((m) => ts.isPropertySignature(m) && keyOf(m) === "directive")?.type;
|
|
186
|
+
return directive && ts.isTypeQueryNode(directive) ? [collapse(directive.exprName.getText())] : [];
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function namesOf(analysis, type) {
|
|
190
|
+
const t = type ? unparen(type) : undefined;
|
|
191
|
+
if (!t || !ts.isTupleTypeNode(t))
|
|
192
|
+
return [];
|
|
193
|
+
return t.elements.map((element) => {
|
|
194
|
+
const e = unparen(element);
|
|
195
|
+
if (!ts.isTypeQueryNode(e))
|
|
196
|
+
return { name: collapse(e.getText()) };
|
|
197
|
+
const target = declarationOf(resolveSymbol(analysis.checker, analysis.checker.getSymbolAtLocation(e.exprName)));
|
|
198
|
+
const file = target && isProjectNode(analysis, target) ? relativeFileOf(analysis, target) : undefined;
|
|
199
|
+
const localName = target && "name" in target && target.name && ts.isIdentifier(target.name)
|
|
200
|
+
? target.name.text
|
|
201
|
+
: undefined;
|
|
202
|
+
return {
|
|
203
|
+
name: collapse(e.exprName.getText()),
|
|
204
|
+
...(file ? { file } : {}),
|
|
205
|
+
...(file && localName ? { localName } : {})
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/** Property types by name; a setter's parameter type wins over the getter's, as it is what an input accepts. */
|
|
210
|
+
function membersOf(node) {
|
|
211
|
+
const out = new Map();
|
|
212
|
+
for (const member of node.members) {
|
|
213
|
+
if (!member.name || !(ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name)))
|
|
214
|
+
continue;
|
|
215
|
+
const name = member.name.text;
|
|
216
|
+
if (ts.isSetAccessorDeclaration(member))
|
|
217
|
+
out.set(name, {
|
|
218
|
+
node: member,
|
|
219
|
+
...(member.parameters[0]?.type ? { type: member.parameters[0].type } : {})
|
|
220
|
+
});
|
|
221
|
+
else if (ts.isPropertyDeclaration(member) || ts.isGetAccessorDeclaration(member)) {
|
|
222
|
+
if (!out.has(name) || !ts.isSetAccessorDeclaration(out.get(name)?.node))
|
|
223
|
+
out.set(name, { node: member, ...(member.type ? { type: member.type } : {}) });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
function docOf(analysis, member) {
|
|
229
|
+
const symbol = member?.name ? analysis.checker.getSymbolAtLocation(member.name) : undefined;
|
|
230
|
+
if (!symbol)
|
|
231
|
+
return {};
|
|
232
|
+
const doc = ts.displayPartsToString(symbol.getDocumentationComment(analysis.checker)).trim();
|
|
233
|
+
const tag = symbol.getJsDocTags(analysis.checker).find((t) => t.name === "deprecated");
|
|
234
|
+
const reason = tag ? ts.displayPartsToString(tag.text).trim() : "";
|
|
235
|
+
return {
|
|
236
|
+
...(doc ? { description: doc } : {}),
|
|
237
|
+
...(tag ? { deprecated: reason ? { reason } : {} } : {})
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function inputOf(analysis, members, property, spec, cls) {
|
|
241
|
+
const member = members.get(property);
|
|
242
|
+
const ref = member?.type ? typeReferenceInfo(member.type) : undefined;
|
|
243
|
+
const signalKind = ref ? SIGNAL_INPUTS[ref.name] : undefined;
|
|
244
|
+
const kind = signalKind ?? (spec.isSignal ? "signal-input" : "input");
|
|
245
|
+
// `InputSignal<T>` and `ModelSignal<T>` wrap the value type; `InputSignalWithTransform<T, W>` also names what is written.
|
|
246
|
+
const valueType = signalKind ? ref?.typeArguments[0] : member?.type;
|
|
247
|
+
const info = valueType
|
|
248
|
+
? typeInfoOf(analysis, valueType)
|
|
249
|
+
: { text: "unknown", kind: "unknown", allowedValues: undefined };
|
|
250
|
+
const accepted = cls.members.find((m) => ts.isPropertyDeclaration(m) &&
|
|
251
|
+
ts.isIdentifier(m.name) &&
|
|
252
|
+
m.name.text === `ngAcceptInputType_${property}`)?.type;
|
|
253
|
+
const writeType = ref?.typeArguments[1] ?? accepted;
|
|
254
|
+
return {
|
|
255
|
+
name: spec.alias,
|
|
256
|
+
property,
|
|
257
|
+
kind,
|
|
258
|
+
typeText: info.text,
|
|
259
|
+
typeKind: info.kind,
|
|
260
|
+
required: spec.required,
|
|
261
|
+
...(info.allowedValues ? { allowedValues: info.allowedValues } : {}),
|
|
262
|
+
...(spec.alias !== property ? { alias: spec.alias } : {}),
|
|
263
|
+
...(writeType ? { writeType: collapse(writeType.getText()) } : {}),
|
|
264
|
+
...docOf(analysis, member?.node),
|
|
265
|
+
node: member?.node ?? cls
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function outputOf(analysis, members, inputs, property, alias, cls) {
|
|
269
|
+
const member = members.get(property);
|
|
270
|
+
const ref = member?.type ? typeReferenceInfo(member.type) : undefined;
|
|
271
|
+
const payload = ref?.typeArguments[0] ? collapse(ref.typeArguments[0].getText()) : undefined;
|
|
272
|
+
// A `model()` declares an input and a `<name>Change` output with no class member of its own.
|
|
273
|
+
const model = member ? undefined : inputs.find((i) => i.kind === "model" && `${i.name}Change` === alias);
|
|
274
|
+
const { description } = docOf(analysis, member?.node);
|
|
275
|
+
return {
|
|
276
|
+
name: alias,
|
|
277
|
+
property,
|
|
278
|
+
kind: model ? "model" : ref?.name === "OutputEmitterRef" ? "signal-output" : "output",
|
|
279
|
+
...(model ? { payloadText: model.typeText } : payload ? { payloadText: payload } : {}),
|
|
280
|
+
...(alias !== property ? { alias } : {}),
|
|
281
|
+
...(description ? { description } : {}),
|
|
282
|
+
node: member?.node ?? model?.node ?? cls
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=declared.js.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { ComponentFacts, excerpt, variantsFromProps } from "../compiler-core/index.js";
|
|
2
|
+
export const CAPABILITIES = [
|
|
3
|
+
"name",
|
|
4
|
+
"implementations.*",
|
|
5
|
+
"props.*",
|
|
6
|
+
"events.*",
|
|
7
|
+
"slots.*",
|
|
8
|
+
"variants.*",
|
|
9
|
+
"accessibility.*",
|
|
10
|
+
"styling.*",
|
|
11
|
+
"composition.*",
|
|
12
|
+
"module.*"
|
|
13
|
+
];
|
|
14
|
+
/** Writes one component or directive with the neutral fact paths shared with the other framework adapters. */
|
|
15
|
+
export function emitComponent(builder, c, pkg) {
|
|
16
|
+
const { meta } = c;
|
|
17
|
+
const ref = builder.entity(c.ref, "component", meta.name);
|
|
18
|
+
const facts = new ComponentFacts(builder, ref, "angular");
|
|
19
|
+
const atNode = (node, method = "ast") => builder.evidence({
|
|
20
|
+
sourceRef: `${meta.file}#${meta.name}`,
|
|
21
|
+
location: {
|
|
22
|
+
type: "file",
|
|
23
|
+
path: node.getSourceFile().fileName.replace(/^\//, ""),
|
|
24
|
+
line: node.getSourceFile().getLineAndCharacterOfPosition(node.getStart()).line + 1
|
|
25
|
+
},
|
|
26
|
+
method,
|
|
27
|
+
original: excerpt(node)
|
|
28
|
+
});
|
|
29
|
+
const atLine = (file, line, original) => builder.evidence({
|
|
30
|
+
sourceRef: `${file}#${meta.name}`,
|
|
31
|
+
location: { type: "file", path: file, line },
|
|
32
|
+
method: "ast",
|
|
33
|
+
...(original ? { original } : {})
|
|
34
|
+
});
|
|
35
|
+
const decl = atNode(meta.node);
|
|
36
|
+
facts.put("name", meta.name, decl);
|
|
37
|
+
facts.link(meta.file, meta.name, decl);
|
|
38
|
+
if (pkg.name)
|
|
39
|
+
facts.put("implementations.angular.packageName", pkg.name, decl);
|
|
40
|
+
const primary = [...c.exports].sort((a, b) => (a.subpath === "." ? 0 : 1) - (b.subpath === "." ? 0 : 1) || (a.name < b.name ? -1 : 1))[0];
|
|
41
|
+
if (primary) {
|
|
42
|
+
facts.put("implementations.angular.exportName", primary.name, decl);
|
|
43
|
+
if (pkg.name)
|
|
44
|
+
facts.put("implementations.angular.importPath", primary.subpath === "." ? pkg.name : `${pkg.name}/${primary.subpath.replace(/^\.\//, "")}`, decl);
|
|
45
|
+
}
|
|
46
|
+
facts.ext("kind", meta.kind, decl);
|
|
47
|
+
facts.ext("exports", c.exports, decl);
|
|
48
|
+
facts.ext("propsComplete", c.propsComplete, decl);
|
|
49
|
+
if (meta.selector)
|
|
50
|
+
facts.ext("selector", meta.selector, decl);
|
|
51
|
+
// `standalone` is recorded only when it is written: its default depends on the Angular version.
|
|
52
|
+
if (meta.standalone !== undefined)
|
|
53
|
+
facts.ext("standalone", meta.standalone, decl);
|
|
54
|
+
if (meta.imports.length > 0)
|
|
55
|
+
facts.ext("imports", meta.imports, decl);
|
|
56
|
+
if (meta.exportAs)
|
|
57
|
+
facts.ext("exportAs", meta.exportAs, decl);
|
|
58
|
+
if (meta.changeDetection)
|
|
59
|
+
facts.ext("changeDetection", meta.changeDetection, decl);
|
|
60
|
+
if (meta.encapsulation)
|
|
61
|
+
facts.ext("encapsulation", meta.encapsulation, decl);
|
|
62
|
+
if (meta.hostDirectives.length > 0)
|
|
63
|
+
facts.ext("hostDirectives", meta.hostDirectives, decl);
|
|
64
|
+
if (meta.inheritsFrom.length > 0)
|
|
65
|
+
facts.ext("inheritedPropsFrom", [...new Set(meta.inheritsFrom)].sort(), decl);
|
|
66
|
+
if (c.template)
|
|
67
|
+
facts.ext("templateSource", c.template.source === "inline" ? "inline" : c.template.file, decl);
|
|
68
|
+
if (c.modules.declaredIn.length > 0)
|
|
69
|
+
facts.ext("declaredIn", c.modules.declaredIn, decl);
|
|
70
|
+
if (c.modules.exportedBy.length > 0)
|
|
71
|
+
facts.ext("exportedBy", c.modules.exportedBy, decl);
|
|
72
|
+
if (meta.queries.length > 0)
|
|
73
|
+
facts.ext("queries", meta.queries, decl);
|
|
74
|
+
const hostListeners = [
|
|
75
|
+
...meta.hostListeners,
|
|
76
|
+
...Object.keys(meta.host)
|
|
77
|
+
.filter((k) => /^\(.+\)$/.test(k))
|
|
78
|
+
.map((k) => k.slice(1, -1))
|
|
79
|
+
].sort();
|
|
80
|
+
const hostBindings = [
|
|
81
|
+
...meta.hostBindings,
|
|
82
|
+
...Object.keys(meta.host)
|
|
83
|
+
.filter((k) => /^\[.+\]$/.test(k))
|
|
84
|
+
.map((k) => k.slice(1, -1))
|
|
85
|
+
].sort();
|
|
86
|
+
if (hostListeners.length > 0)
|
|
87
|
+
facts.ext("host.listeners", hostListeners, decl);
|
|
88
|
+
if (hostBindings.length > 0)
|
|
89
|
+
facts.ext("host.bindings", hostBindings, decl);
|
|
90
|
+
const specs = c.inputs.map((p) => ({
|
|
91
|
+
name: p.name,
|
|
92
|
+
typeText: p.typeText,
|
|
93
|
+
kind: p.typeKind,
|
|
94
|
+
required: p.required,
|
|
95
|
+
...(p.default !== undefined ? { default: p.default } : {}),
|
|
96
|
+
...(p.defaultExpression !== undefined ? { defaultExpression: p.defaultExpression } : {}),
|
|
97
|
+
...(p.allowedValues ? { allowedValues: p.allowedValues } : {}),
|
|
98
|
+
...(p.description ? { description: p.description } : {}),
|
|
99
|
+
...(p.deprecated ? { deprecated: p.deprecated } : {}),
|
|
100
|
+
extensions: {
|
|
101
|
+
kind: p.kind,
|
|
102
|
+
property: p.property,
|
|
103
|
+
...(p.alias ? { alias: p.alias } : {}),
|
|
104
|
+
...(p.transform ? { transform: p.transform } : {}),
|
|
105
|
+
...(p.writeType ? { writeType: p.writeType } : {}),
|
|
106
|
+
...(p.typeInferred ? { typeInferred: true } : {}),
|
|
107
|
+
...(p.inheritedFrom ? { inheritedFrom: p.inheritedFrom } : {})
|
|
108
|
+
}
|
|
109
|
+
}));
|
|
110
|
+
for (const [i, p] of c.inputs.entries()) {
|
|
111
|
+
facts.prop(specs[i], atNode(p.node, p.kind === "input" ? "compiler-api" : "ast"));
|
|
112
|
+
}
|
|
113
|
+
for (const o of c.outputs) {
|
|
114
|
+
facts.event({
|
|
115
|
+
name: o.name,
|
|
116
|
+
...(o.payloadText ? { payloadText: o.payloadText } : {}),
|
|
117
|
+
...(o.description ? { description: o.description } : {}),
|
|
118
|
+
extensions: {
|
|
119
|
+
kind: o.kind,
|
|
120
|
+
property: o.property,
|
|
121
|
+
...(o.alias ? { alias: o.alias } : {}),
|
|
122
|
+
...(o.inheritedFrom ? { inheritedFrom: o.inheritedFrom } : {})
|
|
123
|
+
}
|
|
124
|
+
}, atNode(o.node));
|
|
125
|
+
}
|
|
126
|
+
if (c.template) {
|
|
127
|
+
for (const s of c.template.info.slots) {
|
|
128
|
+
facts.slot({
|
|
129
|
+
name: s.name,
|
|
130
|
+
extensions: { source: "ng-content", ...(s.select !== undefined ? { select: s.select } : {}) }
|
|
131
|
+
}, atLine(c.template.file, s.line));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const s of meta.projection ?? []) {
|
|
135
|
+
facts.slot({
|
|
136
|
+
name: s.name,
|
|
137
|
+
extensions: { source: "ng-content", ...(s.select !== undefined ? { select: s.select } : {}) }
|
|
138
|
+
}, decl);
|
|
139
|
+
}
|
|
140
|
+
for (const v of variantsFromProps(specs)) {
|
|
141
|
+
const input = c.inputs.find((p) => p.name === v.name);
|
|
142
|
+
facts.variant({ ...v, extensions: { source: "prop-union" } }, input ? atNode(input.node) : decl);
|
|
143
|
+
}
|
|
144
|
+
if (c.template && (c.accessibility.roles.length > 0 || c.accessibility.attributes.length > 0)) {
|
|
145
|
+
facts.accessibility({
|
|
146
|
+
...(c.accessibility.roles.length === 1 ? { role: c.accessibility.roles[0] } : {}),
|
|
147
|
+
attributes: c.accessibility.attributes
|
|
148
|
+
}, atLine(c.template.file, c.template.line));
|
|
149
|
+
}
|
|
150
|
+
const s = c.styling;
|
|
151
|
+
if (s.files.length > 0)
|
|
152
|
+
facts.put("styling.cssFiles", s.files, decl);
|
|
153
|
+
if (s.variables.length > 0) {
|
|
154
|
+
facts.put("styling.cssVariables", s.variables.map((v) => v.name), s.variables.map((v) => builder.evidence({
|
|
155
|
+
sourceRef: `${v.file}#${v.name}`,
|
|
156
|
+
location: { type: "file", path: v.file, line: v.line },
|
|
157
|
+
method: "structured-parse",
|
|
158
|
+
original: `var(${v.name})`
|
|
159
|
+
})));
|
|
160
|
+
}
|
|
161
|
+
if (s.preprocessors.length > 0)
|
|
162
|
+
facts.put("styling.preprocessors", s.preprocessors, decl);
|
|
163
|
+
if (c.renders.length > 0)
|
|
164
|
+
facts.put("composition.renders", c.renders, c.template ? atLine(c.template.file, c.template.line) : decl);
|
|
165
|
+
}
|
|
166
|
+
/** An NgModule: what it declares, imports and exports. Names are kept as written. */
|
|
167
|
+
export function emitModule(builder, m) {
|
|
168
|
+
const ref = builder.entity(m.ref, "module", m.meta.name);
|
|
169
|
+
const evidence = builder.evidence({
|
|
170
|
+
sourceRef: `${m.meta.file}#${m.meta.name}`,
|
|
171
|
+
location: { type: "file", path: m.meta.file, line: m.meta.line },
|
|
172
|
+
method: "ast",
|
|
173
|
+
original: excerpt(m.meta.node)
|
|
174
|
+
});
|
|
175
|
+
const put = (path, value) => builder.fact(ref, path, value, evidence, { origin: "extracted" });
|
|
176
|
+
put("module.name", m.meta.name);
|
|
177
|
+
put("module.file", m.meta.file);
|
|
178
|
+
put("module.public", m.public);
|
|
179
|
+
put("module.declarations", m.meta.declarations);
|
|
180
|
+
put("module.imports", m.meta.imports);
|
|
181
|
+
put("module.exports", m.meta.exports);
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=emit.js.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tolerant scanner for Angular templates: finds element start tags with their attributes, and the
|
|
3
|
+
* `<ng-content>` projection slots. It respects quoted attribute values, skips comments and `{{ }}`
|
|
4
|
+
* interpolations, and ignores control-flow blocks (`@if`, `@for`) because they are not tags. It does
|
|
5
|
+
* not build a DOM and does not evaluate expressions.
|
|
6
|
+
*/
|
|
7
|
+
export function scanTemplate(text, firstLine) {
|
|
8
|
+
const elements = [];
|
|
9
|
+
let i = 0;
|
|
10
|
+
let line = firstLine;
|
|
11
|
+
const advance = (to) => {
|
|
12
|
+
for (; i < to; i++)
|
|
13
|
+
if (text[i] === "\n")
|
|
14
|
+
line++;
|
|
15
|
+
};
|
|
16
|
+
while (i < text.length) {
|
|
17
|
+
const c = text[i];
|
|
18
|
+
if (c === "{" && text[i + 1] === "{") {
|
|
19
|
+
const end = text.indexOf("}}", i + 2);
|
|
20
|
+
advance(end < 0 ? text.length : end + 2);
|
|
21
|
+
}
|
|
22
|
+
else if (c === "@" && CONTROL_FLOW.test(text.slice(i, i + 24))) {
|
|
23
|
+
// `@if (a<b) {`: the condition is an expression, not markup, so skip its balanced parentheses.
|
|
24
|
+
advance(skipParentheses(text, text.indexOf("(", i)));
|
|
25
|
+
}
|
|
26
|
+
else if (c === "<" && text.startsWith("<!--", i)) {
|
|
27
|
+
const end = text.indexOf("-->", i + 4);
|
|
28
|
+
advance(end < 0 ? text.length : end + 3);
|
|
29
|
+
}
|
|
30
|
+
else if (c === "<" && /[A-Za-z]/.test(text[i + 1] ?? "")) {
|
|
31
|
+
const start = line;
|
|
32
|
+
const tag = /^[A-Za-z][\w:.-]*/.exec(text.slice(i + 1))?.[0] ?? "";
|
|
33
|
+
let j = i + 1 + tag.length;
|
|
34
|
+
const attributes = [];
|
|
35
|
+
while (j < text.length && text[j] !== ">") {
|
|
36
|
+
if (/\s|\//.test(text[j])) {
|
|
37
|
+
j++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const name = /^[^\s=/>]+/.exec(text.slice(j))?.[0] ?? "";
|
|
41
|
+
j += name.length;
|
|
42
|
+
let value;
|
|
43
|
+
if (text[j] === "=") {
|
|
44
|
+
j++;
|
|
45
|
+
const quote = text[j];
|
|
46
|
+
if (quote === '"' || quote === "'") {
|
|
47
|
+
const end = text.indexOf(quote, j + 1);
|
|
48
|
+
value = text.slice(j + 1, end < 0 ? text.length : end);
|
|
49
|
+
j = end < 0 ? text.length : end + 1;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const unquoted = /^[^\s>]+/.exec(text.slice(j))?.[0] ?? "";
|
|
53
|
+
value = unquoted;
|
|
54
|
+
j += unquoted.length;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (name)
|
|
58
|
+
attributes.push({ ...normalize(name), ...(value !== undefined ? { value } : {}) });
|
|
59
|
+
}
|
|
60
|
+
elements.push({ tag, line: start, attributes });
|
|
61
|
+
advance(Math.min(j + 1, text.length));
|
|
62
|
+
}
|
|
63
|
+
else
|
|
64
|
+
advance(i + 1);
|
|
65
|
+
}
|
|
66
|
+
return { elements, slots: slotsOf(elements) };
|
|
67
|
+
}
|
|
68
|
+
const CONTROL_FLOW = /^@(?:else\s+if|if|for|switch|case|defer)\s*\(/;
|
|
69
|
+
/** Index just past the parenthesis that closes the one at `open`, honoring quotes. */
|
|
70
|
+
function skipParentheses(text, open) {
|
|
71
|
+
let depth = 0;
|
|
72
|
+
for (let i = open; i < text.length; i++) {
|
|
73
|
+
const c = text[i];
|
|
74
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
75
|
+
const end = text.indexOf(c, i + 1);
|
|
76
|
+
if (end < 0)
|
|
77
|
+
return text.length;
|
|
78
|
+
i = end;
|
|
79
|
+
}
|
|
80
|
+
else if (c === "(")
|
|
81
|
+
depth++;
|
|
82
|
+
else if (c === ")" && --depth === 0)
|
|
83
|
+
return i + 1;
|
|
84
|
+
}
|
|
85
|
+
return text.length;
|
|
86
|
+
}
|
|
87
|
+
function normalize(raw) {
|
|
88
|
+
const wrapped = /^\[\(?(.+?)\)?\]$/.exec(raw) ?? /^\((.+)\)$/.exec(raw);
|
|
89
|
+
if (wrapped)
|
|
90
|
+
return { name: wrapped[1].replace(/^attr\./, ""), bound: true };
|
|
91
|
+
if (raw.startsWith("*"))
|
|
92
|
+
return { name: raw.slice(1), bound: true };
|
|
93
|
+
const prefixed = /^(?:bind|on|bindon)-(.+)$/.exec(raw);
|
|
94
|
+
if (prefixed)
|
|
95
|
+
return { name: prefixed[1], bound: true };
|
|
96
|
+
return { name: raw, bound: false };
|
|
97
|
+
}
|
|
98
|
+
function slotsOf(elements) {
|
|
99
|
+
const used = new Set();
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const e of elements) {
|
|
102
|
+
if (e.tag !== "ng-content")
|
|
103
|
+
continue;
|
|
104
|
+
const select = e.attributes.find((a) => a.name === "select" && !a.bound)?.value;
|
|
105
|
+
let name = select === undefined ? "default" : slotName(select);
|
|
106
|
+
for (let n = 2; used.has(name); n++)
|
|
107
|
+
name = `${slotName(select ?? "default")}_${n}`;
|
|
108
|
+
used.add(name);
|
|
109
|
+
out.push({ name, ...(select !== undefined ? { select } : {}), line: e.line });
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
/** `[slot=icon]` and `icon` and `[icon]` and `.icon` all name the slot `icon`; anything else is sanitized. */
|
|
114
|
+
export function slotName(select) {
|
|
115
|
+
const trimmed = select.trim();
|
|
116
|
+
const attrValue = /^\[[\w-]*slot[\w-]*=["']?([\w-]+)["']?\]$/.exec(trimmed);
|
|
117
|
+
if (attrValue)
|
|
118
|
+
return attrValue[1];
|
|
119
|
+
const simple = /^(?:\[([\w-]+)\]|\.([\w-]+)|([A-Za-z][\w-]*))$/.exec(trimmed);
|
|
120
|
+
if (simple)
|
|
121
|
+
return (simple[1] ?? simple[2] ?? simple[3]);
|
|
122
|
+
return trimmed.replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "slot";
|
|
123
|
+
}
|
|
124
|
+
/** Element and attribute parts of a component selector. Class, `:not()` and complex selectors are ignored. */
|
|
125
|
+
export function parseSelector(selector) {
|
|
126
|
+
const out = [];
|
|
127
|
+
for (const alternative of selector.split(",")) {
|
|
128
|
+
const text = alternative.trim();
|
|
129
|
+
if (!text || text.includes(":not(") || text.includes(" ") || text.includes(">"))
|
|
130
|
+
continue;
|
|
131
|
+
const element = /^[A-Za-z][\w-]*/.exec(text)?.[0];
|
|
132
|
+
const attributes = [...text.matchAll(/\[([\w-]+)(?:=[^\]]*)?\]/g)].map((m) => m[1]);
|
|
133
|
+
if (element || attributes.length > 0)
|
|
134
|
+
out.push({ ...(element ? { element } : {}), attributes });
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
export function matchesSelector(element, selectors) {
|
|
139
|
+
const names = new Set(element.attributes.map((a) => a.name));
|
|
140
|
+
return selectors.some((s) => (s.element === undefined || s.element === element.tag) && s.attributes.every((a) => names.has(a)));
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=template.js.map
|