@invariant-ui/cli 0.0.0-stage → 0.2.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 +250 -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/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/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-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/search.js +132 -0
- package/dist/contract-query/system.js +148 -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,52 @@
|
|
|
1
|
+
import { CLI_VERSION, NPM_PACKAGE } from "./version.js";
|
|
2
|
+
export const WORKFLOW_PATH = ".github/workflows/invariant.yml";
|
|
3
|
+
/**
|
|
4
|
+
* Steps that install a project's dependencies, for a project that gets its contract from a package. The package manager
|
|
5
|
+
* is the one the lockfile says. Returns steps already indented for the workflow.
|
|
6
|
+
*/
|
|
7
|
+
export function installStepsFor(has) {
|
|
8
|
+
if (has("pnpm-lock.yaml"))
|
|
9
|
+
return [" - uses: pnpm/action-setup@v4", " - run: pnpm install --frozen-lockfile"];
|
|
10
|
+
if (has("yarn.lock"))
|
|
11
|
+
return [" - run: yarn install --frozen-lockfile"];
|
|
12
|
+
if (has("package-lock.json"))
|
|
13
|
+
return [" - run: npm ci"];
|
|
14
|
+
return [" - run: npm install"];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The GitHub Actions job `setup` offers to add. It runs on every pull request commit, pins the exact version it was
|
|
18
|
+
* written for so a later release cannot change what CI does, and needs only Node: npx fetches the package. Paths are
|
|
19
|
+
* quoted, since they come from the project's folder names.
|
|
20
|
+
*/
|
|
21
|
+
export function workflowYaml(paths, installSteps = []) {
|
|
22
|
+
const targets = (paths.length > 0 ? paths : ["src"]).map((p) => `'${p.replace(/'/g, "''")}'`).join(" ");
|
|
23
|
+
const run = `npx ${NPM_PACKAGE}@${CLI_VERSION}`;
|
|
24
|
+
return `# Added by \`invariant setup\`. Checks every commit on a pull request against your design system contract.
|
|
25
|
+
name: Invariant
|
|
26
|
+
on:
|
|
27
|
+
pull_request:
|
|
28
|
+
types: [opened, synchronize, reopened]
|
|
29
|
+
push:
|
|
30
|
+
branches: [main]
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
concurrency:
|
|
34
|
+
group: invariant-\${{ github.ref }}
|
|
35
|
+
cancel-in-progress: true
|
|
36
|
+
jobs:
|
|
37
|
+
invariant:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
- uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: 22
|
|
44
|
+
${installSteps.length > 0 ? `${installSteps.join("\n")}\n` : ""} # Fails if the committed contract (the invariant/ folder) is stale or was edited by hand.
|
|
45
|
+
- name: Verify the contract is current
|
|
46
|
+
run: ${run} verify --fresh
|
|
47
|
+
# Annotates the pull request. Violations listed in invariant.baseline.json are hidden; new ones fail the check.
|
|
48
|
+
- name: Validate code against the contract
|
|
49
|
+
run: ${run} validate ${targets} --format github
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=workflow.js.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { DEFAULT_IGNORED_DIRS, hashText, IrBuilder, walk } from "../compiler-core/index.js";
|
|
2
|
+
import { hashJson } from "../shared/index.js";
|
|
3
|
+
import { analyzePackage } from "./analyze.js";
|
|
4
|
+
import { CAPABILITIES, emitComponent, emitModule } from "./emit.js";
|
|
5
|
+
export const ADAPTER_ID = "angular";
|
|
6
|
+
export const ADAPTER_VERSION = "0.1.0";
|
|
7
|
+
const SOURCE_FILE = /\.(?:[cm]?[jt]sx?|html|css|scss|sass|less)$|(?:^|\/)tsconfig[^/]*\.json$/;
|
|
8
|
+
const MAX_FILE_BYTES = 1_000_000;
|
|
9
|
+
/**
|
|
10
|
+
* Angular adapter. For every workspace package that declares `@angular/core` it reads decorated classes
|
|
11
|
+
* with the TypeScript API (what the Angular compiler reads from decorators, evaluated statically):
|
|
12
|
+
* components and directives, decorator and signal inputs and outputs, `model()`, selectors, standalone
|
|
13
|
+
* metadata and imports, content projection, host bindings, templates and styles, and NgModules.
|
|
14
|
+
* Nothing is executed. `node_modules` is read only for a package opted in as a declaration root, from its `.d.ts` files.
|
|
15
|
+
*/
|
|
16
|
+
export const angularAdapter = {
|
|
17
|
+
id: ADAPTER_ID,
|
|
18
|
+
version: ADAPTER_VERSION,
|
|
19
|
+
type: "angular",
|
|
20
|
+
async discover({ fs, project }) {
|
|
21
|
+
const sources = [];
|
|
22
|
+
const used = new Set();
|
|
23
|
+
for (const pkg of project.packages.filter((p) => p.frameworks.some((f) => f.name === "angular"))) {
|
|
24
|
+
const others = project.packages.filter((p) => p.id !== pkg.id && p.dir !== "." && (pkg.dir === "." || p.dir.startsWith(`${pkg.dir}/`)));
|
|
25
|
+
const { files, hashes } = await readPackageFiles(fs, pkg, others);
|
|
26
|
+
const slug = (pkg.name ?? pkg.dir)
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
29
|
+
.replace(/^[^a-z0-9]+|[-.]+$/g, "") || "root";
|
|
30
|
+
let id = `angular-${slug}`;
|
|
31
|
+
for (let n = 2; used.has(id); n++)
|
|
32
|
+
id = `angular-${slug}-${n}`;
|
|
33
|
+
used.add(id);
|
|
34
|
+
sources.push({
|
|
35
|
+
id,
|
|
36
|
+
type: "angular",
|
|
37
|
+
location: pkg.dir,
|
|
38
|
+
config: { packageId: pkg.id, files: [...files.keys()] },
|
|
39
|
+
...(pkg.version ? { version: { kind: "package-version", value: pkg.version } } : {}),
|
|
40
|
+
inputHash: hashJson(hashes)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return { sources, diagnostics: [] };
|
|
44
|
+
},
|
|
45
|
+
async extract({ fs, project, source, clock }, config) {
|
|
46
|
+
const pkg = project.packages.find((p) => p.id === config.packageId);
|
|
47
|
+
if (!pkg)
|
|
48
|
+
throw new Error(`Package ${config.packageId} is not in the project discovery`);
|
|
49
|
+
const files = new Map();
|
|
50
|
+
for (const path of config.files)
|
|
51
|
+
files.set(path, await fs.readFile(path));
|
|
52
|
+
const analysis = analyzePackage({
|
|
53
|
+
...(pkg.name ? { packageName: pkg.name } : {}),
|
|
54
|
+
packageDir: pkg.dir,
|
|
55
|
+
entryPoints: pkg.entryPoints,
|
|
56
|
+
files
|
|
57
|
+
});
|
|
58
|
+
const builder = new IrBuilder({
|
|
59
|
+
source: {
|
|
60
|
+
id: source.id,
|
|
61
|
+
type: source.type,
|
|
62
|
+
location: source.location,
|
|
63
|
+
...(source.version ? { version: source.version } : {}),
|
|
64
|
+
inputHash: source.inputHash,
|
|
65
|
+
adapter: { name: ADAPTER_ID, version: ADAPTER_VERSION }
|
|
66
|
+
},
|
|
67
|
+
extractor: { name: "angular-ts", version: ADAPTER_VERSION },
|
|
68
|
+
capabilities: CAPABILITIES,
|
|
69
|
+
clock
|
|
70
|
+
});
|
|
71
|
+
for (const component of analysis.components)
|
|
72
|
+
emitComponent(builder, component, pkg);
|
|
73
|
+
for (const module of analysis.modules)
|
|
74
|
+
emitModule(builder, module);
|
|
75
|
+
for (const d of analysis.diagnostics)
|
|
76
|
+
builder.diagnostic(d);
|
|
77
|
+
return { ir: builder.build(analysis.components.some((c) => !c.propsComplete) ? "partial" : "complete") };
|
|
78
|
+
},
|
|
79
|
+
async validate({ ir }) {
|
|
80
|
+
return !ir.entities.some((e) => e.kind === "component")
|
|
81
|
+
? [
|
|
82
|
+
{
|
|
83
|
+
code: "angular.no-components",
|
|
84
|
+
severity: "info",
|
|
85
|
+
message: `No Angular components or directives were found in the entry points of ${ir.source.id}`,
|
|
86
|
+
sourceId: ir.source.id
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
: [];
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
async function readPackageFiles(fs, pkg, nested) {
|
|
93
|
+
// An installed package (opted in through a declaration root) keeps its declaration files in build output.
|
|
94
|
+
const installed = pkg.dir.split("/").includes("node_modules");
|
|
95
|
+
const walked = await walk(fs, {
|
|
96
|
+
ignoreDirs: new Set(installed
|
|
97
|
+
? DEFAULT_IGNORED_DIRS.filter((d) => !["dist", "build", "out"].includes(d))
|
|
98
|
+
: DEFAULT_IGNORED_DIRS),
|
|
99
|
+
maxFiles: 20_000,
|
|
100
|
+
start: pkg.dir
|
|
101
|
+
});
|
|
102
|
+
const files = new Map();
|
|
103
|
+
const hashes = [];
|
|
104
|
+
for (const path of walked.files) {
|
|
105
|
+
if (!SOURCE_FILE.test(path) || nested.some((n) => path === n.dir || path.startsWith(`${n.dir}/`)))
|
|
106
|
+
continue;
|
|
107
|
+
const text = await fs.readFile(path);
|
|
108
|
+
if (text.length > MAX_FILE_BYTES)
|
|
109
|
+
continue;
|
|
110
|
+
files.set(path, text);
|
|
111
|
+
hashes.push([path, hashText(text)]);
|
|
112
|
+
}
|
|
113
|
+
return { files, hashes };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { createAnalysis, cssVariableReferences, declarationOf, isProjectNode, posix, resolveSymbol, tsconfigPaths } from "../compiler-core/index.js";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { readClass } from "./classes.js";
|
|
4
|
+
import { readDeclaredClass } from "./declared.js";
|
|
5
|
+
import { matchesSelector, parseSelector, scanTemplate } from "./template.js";
|
|
6
|
+
const NON_SOURCE = /\.(?:test|spec|stories)\.[cm]?[jt]sx?$|(?:^|\/)__(?:tests|mocks)__\//;
|
|
7
|
+
/** Public components and directives (class exports of the entry points) plus every NgModule, read statically. */
|
|
8
|
+
export function analyzePackage(input) {
|
|
9
|
+
const diagnostics = [];
|
|
10
|
+
const note = (severity, code, message, location) => {
|
|
11
|
+
diagnostics.push({ code, severity, message, ...(location ? { location } : {}) });
|
|
12
|
+
};
|
|
13
|
+
const codeFiles = new Map([...input.files].filter(([path]) => /\.[cm]?[jt]sx?$/.test(path) && !NON_SOURCE.test(path)));
|
|
14
|
+
// A library imports its own entry points by package name (`@scope/lib/accordion`): map those to files.
|
|
15
|
+
const paths = { ...tsconfigPaths(input.files, input.packageDir) };
|
|
16
|
+
if (input.packageName) {
|
|
17
|
+
for (const entry of input.entryPoints) {
|
|
18
|
+
if (entry.file)
|
|
19
|
+
paths[entry.subpath === "."
|
|
20
|
+
? input.packageName
|
|
21
|
+
: `${input.packageName}/${entry.subpath.replace(/^\.\//, "")}`] = [entry.file];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const analysis = createAnalysis(codeFiles, { paths });
|
|
25
|
+
const publicClasses = new Map();
|
|
26
|
+
for (const entry of input.entryPoints) {
|
|
27
|
+
const sf = entry.file ? analysis.sourceFile(entry.file) : undefined;
|
|
28
|
+
const moduleSymbol = sf ? analysis.checker.getSymbolAtLocation(sf) : undefined;
|
|
29
|
+
if (!sf || !moduleSymbol)
|
|
30
|
+
continue;
|
|
31
|
+
for (const exported of analysis.checker.getExportsOfModule(moduleSymbol)) {
|
|
32
|
+
const decl = declarationOf(resolveSymbol(analysis.checker, exported));
|
|
33
|
+
if (decl && ts.isClassDeclaration(decl) && isProjectNode(analysis, decl))
|
|
34
|
+
publicClasses.set(decl, [
|
|
35
|
+
...(publicClasses.get(decl) ?? []),
|
|
36
|
+
{ subpath: entry.subpath, name: exported.name }
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const metas = new Map();
|
|
41
|
+
const modules = [];
|
|
42
|
+
for (const sf of analysis.program.getSourceFiles()) {
|
|
43
|
+
if (!analysis.relativePath(sf.fileName))
|
|
44
|
+
continue;
|
|
45
|
+
for (const statement of sf.statements) {
|
|
46
|
+
if (!ts.isClassDeclaration(statement))
|
|
47
|
+
continue;
|
|
48
|
+
const meta = readClass(analysis, statement) ?? readDeclaredClass(analysis, statement);
|
|
49
|
+
if (!meta)
|
|
50
|
+
continue;
|
|
51
|
+
metas.set(statement, meta);
|
|
52
|
+
if (meta.kind === "module")
|
|
53
|
+
modules.push({ ref: `angular:${meta.file}#${meta.name}`, meta, public: publicClasses.has(statement) });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const publicComponents = [...publicClasses.keys()].flatMap((decl) => {
|
|
57
|
+
const meta = metas.get(decl);
|
|
58
|
+
return meta && meta.kind !== "module" ? [{ decl, meta }] : [];
|
|
59
|
+
});
|
|
60
|
+
const selectors = new Map();
|
|
61
|
+
const refs = new Map();
|
|
62
|
+
for (const { decl, meta } of publicComponents) {
|
|
63
|
+
refs.set(decl, `angular:${meta.file}#${meta.name}`);
|
|
64
|
+
if (meta.selector)
|
|
65
|
+
selectors.set(refs.get(decl), parseSelector(meta.selector));
|
|
66
|
+
}
|
|
67
|
+
const components = publicComponents.map(({ decl, meta }) => {
|
|
68
|
+
const ref = refs.get(decl);
|
|
69
|
+
const template = readTemplate(meta, input.files, note);
|
|
70
|
+
const styling = readStyling(meta, input.files, note);
|
|
71
|
+
const renders = new Map();
|
|
72
|
+
for (const element of template?.info.elements ?? []) {
|
|
73
|
+
for (const [targetRef, parsed] of selectors) {
|
|
74
|
+
if (targetRef !== ref && matchesSelector(element, parsed))
|
|
75
|
+
renders.set(targetRef, (renders.get(targetRef) ?? 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const { inputs, outputs, complete } = withMetadataMembers(meta);
|
|
79
|
+
if (!meta.selector && meta.kind === "component")
|
|
80
|
+
note("info", "angular.no-selector", `${meta.name} has no static selector`, {
|
|
81
|
+
path: meta.file,
|
|
82
|
+
line: meta.line
|
|
83
|
+
});
|
|
84
|
+
for (const message of meta.notes)
|
|
85
|
+
note("info", "angular.unsupported", `${meta.name}: ${message}`, { path: meta.file, line: meta.line });
|
|
86
|
+
if (meta.inheritsFrom.length > 0)
|
|
87
|
+
note("info", "angular.base-class-external", `${meta.name} extends ${meta.inheritsFrom.join(", ")}, whose inputs and outputs are not enumerated`, { path: meta.file, line: meta.line });
|
|
88
|
+
const a11y = accessibilityOf(template?.info);
|
|
89
|
+
return {
|
|
90
|
+
ref,
|
|
91
|
+
meta,
|
|
92
|
+
exports: (publicClasses.get(decl) ?? []).sort((a, b) => a.subpath < b.subpath ? -1 : a.subpath > b.subpath ? 1 : a.name < b.name ? -1 : 1),
|
|
93
|
+
inputs,
|
|
94
|
+
outputs,
|
|
95
|
+
...(template ? { template } : {}),
|
|
96
|
+
accessibility: a11y,
|
|
97
|
+
styling,
|
|
98
|
+
renders: [...renders]
|
|
99
|
+
.map(([target, count]) => ({ target, count }))
|
|
100
|
+
.sort((a, b) => (a.target < b.target ? -1 : 1)),
|
|
101
|
+
modules: modulesOf(meta, modules),
|
|
102
|
+
propsComplete: complete && meta.inheritsFrom.length === 0 && meta.hostDirectives.length === 0
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
components.sort((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
|
|
106
|
+
modules.sort((a, b) => (a.ref < b.ref ? -1 : 1));
|
|
107
|
+
return { components, modules, diagnostics };
|
|
108
|
+
}
|
|
109
|
+
function readTemplate(meta, files, note) {
|
|
110
|
+
const template = meta.template;
|
|
111
|
+
if (!template)
|
|
112
|
+
return undefined;
|
|
113
|
+
if ("inline" in template)
|
|
114
|
+
return {
|
|
115
|
+
source: "inline",
|
|
116
|
+
file: meta.file,
|
|
117
|
+
line: template.line,
|
|
118
|
+
info: scanTemplate(template.inline, template.line)
|
|
119
|
+
};
|
|
120
|
+
const file = posix.normalize(posix.join(posix.dirname(meta.file), template.url));
|
|
121
|
+
const text = files.get(file);
|
|
122
|
+
if (text === undefined) {
|
|
123
|
+
note("warning", "angular.templateurl-missing", `${meta.name}: template file ${file} was not found`, {
|
|
124
|
+
path: meta.file,
|
|
125
|
+
line: meta.line
|
|
126
|
+
});
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
return { source: "file", file, line: 1, info: scanTemplate(text, 1) };
|
|
130
|
+
}
|
|
131
|
+
function readStyling(meta, files, note) {
|
|
132
|
+
const variables = new Map();
|
|
133
|
+
const styleFiles = [];
|
|
134
|
+
for (const s of meta.styles) {
|
|
135
|
+
for (const ref of cssVariableReferences(s.text))
|
|
136
|
+
if (!variables.has(ref.name))
|
|
137
|
+
variables.set(ref.name, { file: meta.file, line: s.line + ref.line - 1 });
|
|
138
|
+
}
|
|
139
|
+
for (const url of meta.styleUrls) {
|
|
140
|
+
const file = posix.normalize(posix.join(posix.dirname(meta.file), url));
|
|
141
|
+
const text = files.get(file);
|
|
142
|
+
if (text === undefined) {
|
|
143
|
+
note("warning", "angular.styleurl-missing", `${meta.name}: stylesheet ${file} was not found`, {
|
|
144
|
+
path: meta.file,
|
|
145
|
+
line: meta.line
|
|
146
|
+
});
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
styleFiles.push(file);
|
|
150
|
+
for (const ref of cssVariableReferences(text))
|
|
151
|
+
if (!variables.has(ref.name))
|
|
152
|
+
variables.set(ref.name, { file, line: ref.line });
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
files: styleFiles.sort(),
|
|
156
|
+
variables: [...variables].map(([name, v]) => ({ name, ...v })).sort((a, b) => (a.name < b.name ? -1 : 1)),
|
|
157
|
+
preprocessors: [
|
|
158
|
+
...new Set(styleFiles.flatMap((f) => /\.(scss|sass|less)$/.exec(f)?.[1] ? [/\.(scss|sass|less)$/.exec(f)?.[1]] : []))
|
|
159
|
+
].sort()
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function accessibilityOf(info) {
|
|
163
|
+
const roles = new Set();
|
|
164
|
+
const attributes = new Set();
|
|
165
|
+
for (const element of info?.elements ?? []) {
|
|
166
|
+
// Native elements only: custom elements (with a dash) are Angular components.
|
|
167
|
+
if (element.tag.includes("-") || /^[A-Z]/.test(element.tag) || element.tag === "ng-content")
|
|
168
|
+
continue;
|
|
169
|
+
for (const a of element.attributes) {
|
|
170
|
+
if (a.name === "role" && !a.bound && a.value)
|
|
171
|
+
roles.add(a.value);
|
|
172
|
+
if (a.name.startsWith("aria-"))
|
|
173
|
+
attributes.add(a.name);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { roles: [...roles].sort(), attributes: [...attributes].sort() };
|
|
177
|
+
}
|
|
178
|
+
/** `inputs: ['a', 'b: alias']` and `outputs: [...]` in the decorator, for names with no declared member. */
|
|
179
|
+
function withMetadataMembers(meta) {
|
|
180
|
+
const inputs = [...meta.inputs];
|
|
181
|
+
const outputs = [...meta.outputs];
|
|
182
|
+
for (const spec of meta.metadataInputs) {
|
|
183
|
+
const [property = "", alias] = spec.split(":").map((s) => s.trim());
|
|
184
|
+
const name = alias || property;
|
|
185
|
+
if (!property || inputs.some((i) => i.name === name))
|
|
186
|
+
continue;
|
|
187
|
+
inputs.push({
|
|
188
|
+
name,
|
|
189
|
+
property,
|
|
190
|
+
kind: "input",
|
|
191
|
+
typeText: "unknown",
|
|
192
|
+
typeKind: "unknown",
|
|
193
|
+
required: false,
|
|
194
|
+
...(alias ? { alias } : {}),
|
|
195
|
+
node: meta.node
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
for (const spec of meta.metadataOutputs) {
|
|
199
|
+
const [property = "", alias] = spec.split(":").map((s) => s.trim());
|
|
200
|
+
const name = alias || property;
|
|
201
|
+
if (!property || outputs.some((o) => o.name === name))
|
|
202
|
+
continue;
|
|
203
|
+
outputs.push({ name, property, kind: "output", ...(alias ? { alias } : {}), node: meta.node });
|
|
204
|
+
}
|
|
205
|
+
inputs.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
206
|
+
outputs.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
207
|
+
return { inputs, outputs, complete: true };
|
|
208
|
+
}
|
|
209
|
+
function modulesOf(meta, modules) {
|
|
210
|
+
const matches = (refs) => refs.some((r) => r.file === meta.file && r.localName === meta.name);
|
|
211
|
+
return {
|
|
212
|
+
declaredIn: modules
|
|
213
|
+
.filter((m) => matches(m.meta.declarations))
|
|
214
|
+
.map((m) => m.meta.name)
|
|
215
|
+
.sort(),
|
|
216
|
+
exportedBy: modules
|
|
217
|
+
.filter((m) => matches(m.meta.exports))
|
|
218
|
+
.map((m) => m.meta.name)
|
|
219
|
+
.sort()
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=analyze.js.map
|