@intentius/chant 0.0.1
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 +365 -0
- package/package.json +22 -0
- package/src/attrref.test.ts +148 -0
- package/src/attrref.ts +50 -0
- package/src/barrel.test.ts +157 -0
- package/src/barrel.ts +101 -0
- package/src/bench.test.ts +227 -0
- package/src/build.test.ts +437 -0
- package/src/build.ts +425 -0
- package/src/builder.test.ts +312 -0
- package/src/builder.ts +56 -0
- package/src/child-project.ts +44 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/README.md +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +7 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/examples/getting-started/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/justfile +26 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +29 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +8 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +74 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/naming.ts +33 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +45 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/coverage.ts +11 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/generated/.gitkeep +0 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/generator.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/import/parser.ts +10 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/index.ts +9 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/index.ts +1 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lint/rules/sample.ts +18 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/completions.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/lsp/hover.ts +14 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +110 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/serializer.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/fetch.ts +21 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/spec/parse.ts +25 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate-cli.ts +4 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +24 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/tsconfig.json +10 -0
- package/src/cli/commands/__fixtures__/sample-rule.ts +11 -0
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +222 -0
- package/src/cli/commands/build.test.ts +149 -0
- package/src/cli/commands/build.ts +344 -0
- package/src/cli/commands/diff.test.ts +148 -0
- package/src/cli/commands/diff.ts +221 -0
- package/src/cli/commands/doctor.test.ts +239 -0
- package/src/cli/commands/doctor.ts +224 -0
- package/src/cli/commands/import.test.ts +379 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/init-lexicon.test.ts +297 -0
- package/src/cli/commands/init-lexicon.ts +993 -0
- package/src/cli/commands/init.test.ts +317 -0
- package/src/cli/commands/init.ts +505 -0
- package/src/cli/commands/licenses.ts +165 -0
- package/src/cli/commands/lint.test.ts +332 -0
- package/src/cli/commands/lint.ts +408 -0
- package/src/cli/commands/list.test.ts +100 -0
- package/src/cli/commands/list.ts +108 -0
- package/src/cli/commands/update.test.ts +38 -0
- package/src/cli/commands/update.ts +207 -0
- package/src/cli/conflict-check.test.ts +255 -0
- package/src/cli/conflict-check.ts +89 -0
- package/src/cli/debug.ts +8 -0
- package/src/cli/format.test.ts +140 -0
- package/src/cli/format.ts +133 -0
- package/src/cli/handlers/build.ts +58 -0
- package/src/cli/handlers/dev.ts +38 -0
- package/src/cli/handlers/init.ts +46 -0
- package/src/cli/handlers/lint.ts +36 -0
- package/src/cli/handlers/misc.ts +57 -0
- package/src/cli/handlers/serve.ts +26 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/lsp/capabilities.ts +46 -0
- package/src/cli/lsp/diagnostics.ts +52 -0
- package/src/cli/lsp/server.test.ts +618 -0
- package/src/cli/lsp/server.ts +393 -0
- package/src/cli/main.test.ts +257 -0
- package/src/cli/main.ts +224 -0
- package/src/cli/mcp/resources/context.ts +59 -0
- package/src/cli/mcp/server.test.ts +747 -0
- package/src/cli/mcp/server.ts +402 -0
- package/src/cli/mcp/tools/build.ts +117 -0
- package/src/cli/mcp/tools/import.ts +48 -0
- package/src/cli/mcp/tools/lint.ts +45 -0
- package/src/cli/plugins.test.ts +31 -0
- package/src/cli/plugins.ts +94 -0
- package/src/cli/registry.ts +73 -0
- package/src/cli/reporters/stylish.test.ts +282 -0
- package/src/cli/reporters/stylish.ts +186 -0
- package/src/cli/watch.test.ts +81 -0
- package/src/cli/watch.ts +101 -0
- package/src/codegen/case.test.ts +30 -0
- package/src/codegen/case.ts +11 -0
- package/src/codegen/coverage.ts +167 -0
- package/src/codegen/docs.ts +634 -0
- package/src/codegen/fetch.test.ts +119 -0
- package/src/codegen/fetch.ts +261 -0
- package/src/codegen/generate-registry.test.ts +118 -0
- package/src/codegen/generate-registry.ts +107 -0
- package/src/codegen/generate-runtime-index.test.ts +81 -0
- package/src/codegen/generate-runtime-index.ts +99 -0
- package/src/codegen/generate-typescript.test.ts +146 -0
- package/src/codegen/generate-typescript.ts +161 -0
- package/src/codegen/generate.ts +206 -0
- package/src/codegen/json-patch.test.ts +113 -0
- package/src/codegen/json-patch.ts +151 -0
- package/src/codegen/json-schema.test.ts +196 -0
- package/src/codegen/json-schema.ts +209 -0
- package/src/codegen/naming.ts +201 -0
- package/src/codegen/package.ts +161 -0
- package/src/codegen/rollback.test.ts +92 -0
- package/src/codegen/rollback.ts +115 -0
- package/src/codegen/topo-sort.test.ts +69 -0
- package/src/codegen/topo-sort.ts +46 -0
- package/src/codegen/typecheck.test.ts +37 -0
- package/src/codegen/typecheck.ts +74 -0
- package/src/codegen/validate.test.ts +86 -0
- package/src/codegen/validate.ts +143 -0
- package/src/composite.test.ts +426 -0
- package/src/composite.ts +243 -0
- package/src/config.test.ts +91 -0
- package/src/config.ts +87 -0
- package/src/declarable.test.ts +160 -0
- package/src/declarable.ts +47 -0
- package/src/detectLexicon.test.ts +236 -0
- package/src/detectLexicon.ts +37 -0
- package/src/discovery/cache.test.ts +78 -0
- package/src/discovery/cache.ts +86 -0
- package/src/discovery/collect.test.ts +269 -0
- package/src/discovery/collect.ts +51 -0
- package/src/discovery/cycles.test.ts +238 -0
- package/src/discovery/cycles.ts +107 -0
- package/src/discovery/files.test.ts +154 -0
- package/src/discovery/files.ts +61 -0
- package/src/discovery/graph.test.ts +476 -0
- package/src/discovery/graph.ts +150 -0
- package/src/discovery/import.test.ts +199 -0
- package/src/discovery/import.ts +20 -0
- package/src/discovery/index.test.ts +272 -0
- package/src/discovery/index.ts +132 -0
- package/src/discovery/resolve.test.ts +267 -0
- package/src/discovery/resolve.ts +54 -0
- package/src/errors.test.ts +138 -0
- package/src/errors.ts +86 -0
- package/src/import/base-parser.test.ts +67 -0
- package/src/import/base-parser.ts +48 -0
- package/src/import/generator.ts +21 -0
- package/src/import/ir-utils.test.ts +103 -0
- package/src/import/ir-utils.ts +87 -0
- package/src/import/parser.ts +41 -0
- package/src/index.ts +60 -0
- package/src/intrinsic-interpolation.test.ts +91 -0
- package/src/intrinsic-interpolation.ts +89 -0
- package/src/intrinsic.test.ts +69 -0
- package/src/intrinsic.ts +43 -0
- package/src/lexicon-integrity.test.ts +94 -0
- package/src/lexicon-integrity.ts +69 -0
- package/src/lexicon-manifest.test.ts +101 -0
- package/src/lexicon-manifest.ts +71 -0
- package/src/lexicon-output.test.ts +182 -0
- package/src/lexicon-output.ts +82 -0
- package/src/lexicon-schema.test.ts +239 -0
- package/src/lexicon-schema.ts +144 -0
- package/src/lexicon.ts +212 -0
- package/src/lint/config-overrides.test.ts +254 -0
- package/src/lint/config.test.ts +644 -0
- package/src/lint/config.ts +375 -0
- package/src/lint/declarative.test.ts +256 -0
- package/src/lint/declarative.ts +187 -0
- package/src/lint/engine.test.ts +465 -0
- package/src/lint/engine.ts +172 -0
- package/src/lint/named-checks.test.ts +37 -0
- package/src/lint/named-checks.ts +33 -0
- package/src/lint/parser.test.ts +129 -0
- package/src/lint/parser.ts +42 -0
- package/src/lint/post-synth.test.ts +113 -0
- package/src/lint/post-synth.ts +76 -0
- package/src/lint/presets/relaxed.json +19 -0
- package/src/lint/presets/strict.json +19 -0
- package/src/lint/rule-loader.test.ts +67 -0
- package/src/lint/rule-loader.ts +67 -0
- package/src/lint/rule-options.test.ts +141 -0
- package/src/lint/rule.test.ts +196 -0
- package/src/lint/rule.ts +98 -0
- package/src/lint/rules/barrel-import-style.test.ts +80 -0
- package/src/lint/rules/barrel-import-style.ts +59 -0
- package/src/lint/rules/composite-scope.ts +55 -0
- package/src/lint/rules/cor017-composite-name-match.test.ts +107 -0
- package/src/lint/rules/cor017-composite-name-match.ts +108 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.test.ts +172 -0
- package/src/lint/rules/cor018-composite-prefer-lexicon-type.ts +167 -0
- package/src/lint/rules/declarable-naming-convention.test.ts +69 -0
- package/src/lint/rules/declarable-naming-convention.ts +70 -0
- package/src/lint/rules/enforce-barrel-import.test.ts +169 -0
- package/src/lint/rules/enforce-barrel-import.ts +81 -0
- package/src/lint/rules/enforce-barrel-ref.test.ts +114 -0
- package/src/lint/rules/enforce-barrel-ref.ts +75 -0
- package/src/lint/rules/evl001-non-literal-expression.test.ts +158 -0
- package/src/lint/rules/evl001-non-literal-expression.ts +149 -0
- package/src/lint/rules/evl002-control-flow-resource.test.ts +110 -0
- package/src/lint/rules/evl002-control-flow-resource.ts +61 -0
- package/src/lint/rules/evl003-dynamic-property-access.test.ts +63 -0
- package/src/lint/rules/evl003-dynamic-property-access.ts +41 -0
- package/src/lint/rules/evl004-spread-non-const.test.ts +130 -0
- package/src/lint/rules/evl004-spread-non-const.ts +111 -0
- package/src/lint/rules/evl005-resource-block-body.test.ts +59 -0
- package/src/lint/rules/evl005-resource-block-body.ts +49 -0
- package/src/lint/rules/evl006-barrel-usage.test.ts +63 -0
- package/src/lint/rules/evl006-barrel-usage.ts +95 -0
- package/src/lint/rules/evl007-invalid-siblings.test.ts +87 -0
- package/src/lint/rules/evl007-invalid-siblings.ts +139 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +118 -0
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +140 -0
- package/src/lint/rules/evl009-composite-no-constant.test.ts +162 -0
- package/src/lint/rules/evl009-composite-no-constant.ts +171 -0
- package/src/lint/rules/evl010-composite-no-transform.test.ts +121 -0
- package/src/lint/rules/evl010-composite-no-transform.ts +69 -0
- package/src/lint/rules/export-required.test.ts +213 -0
- package/src/lint/rules/export-required.ts +158 -0
- package/src/lint/rules/file-declarable-limit.test.ts +148 -0
- package/src/lint/rules/file-declarable-limit.ts +96 -0
- package/src/lint/rules/flat-declarations.test.ts +210 -0
- package/src/lint/rules/flat-declarations.ts +70 -0
- package/src/lint/rules/index.ts +99 -0
- package/src/lint/rules/no-cyclic-declarable-ref.test.ts +135 -0
- package/src/lint/rules/no-cyclic-declarable-ref.ts +178 -0
- package/src/lint/rules/no-redundant-type-import.test.ts +129 -0
- package/src/lint/rules/no-redundant-type-import.ts +85 -0
- package/src/lint/rules/no-redundant-value-cast.test.ts +51 -0
- package/src/lint/rules/no-redundant-value-cast.ts +46 -0
- package/src/lint/rules/no-string-ref.test.ts +100 -0
- package/src/lint/rules/no-string-ref.ts +66 -0
- package/src/lint/rules/no-unused-declarable-import.test.ts +74 -0
- package/src/lint/rules/no-unused-declarable-import.ts +103 -0
- package/src/lint/rules/no-unused-declarable.test.ts +134 -0
- package/src/lint/rules/no-unused-declarable.ts +118 -0
- package/src/lint/rules/prefer-namespace-import.test.ts +102 -0
- package/src/lint/rules/prefer-namespace-import.ts +63 -0
- package/src/lint/rules/single-concern-file.test.ts +156 -0
- package/src/lint/rules/single-concern-file.ts +98 -0
- package/src/lint/rules/stale-barrel-types.ts +60 -0
- package/src/lint/selectors.test.ts +113 -0
- package/src/lint/selectors.ts +188 -0
- package/src/lsp/lexicon-providers.ts +191 -0
- package/src/lsp/types.ts +79 -0
- package/src/mcp/types.ts +22 -0
- package/src/project/scan.test.ts +178 -0
- package/src/project/scan.ts +182 -0
- package/src/project/sync.test.ts +87 -0
- package/src/project/sync.ts +46 -0
- package/src/project-validation.test.ts +64 -0
- package/src/project-validation.ts +79 -0
- package/src/pseudo-parameter.test.ts +39 -0
- package/src/pseudo-parameter.ts +47 -0
- package/src/runtime.ts +68 -0
- package/src/serializer-walker.test.ts +124 -0
- package/src/serializer-walker.ts +83 -0
- package/src/serializer.ts +42 -0
- package/src/sort.test.ts +290 -0
- package/src/sort.ts +58 -0
- package/src/stack-output.ts +82 -0
- package/src/types.test.ts +307 -0
- package/src/types.ts +46 -0
- package/src/utils.test.ts +195 -0
- package/src/utils.ts +46 -0
- package/src/validation.test.ts +308 -0
- package/src/validation.ts +50 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A selector function that extracts matching nodes from a source file.
|
|
5
|
+
*/
|
|
6
|
+
export type SelectorFn = (sourceFile: ts.SourceFile) => ts.Node[];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Registry of named selectors.
|
|
10
|
+
*/
|
|
11
|
+
const selectorRegistry = new Map<string, SelectorFn>();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Recursively collect nodes matching a predicate.
|
|
15
|
+
*/
|
|
16
|
+
export function collectNodes(root: ts.Node, predicate: (node: ts.Node) => boolean): ts.Node[] {
|
|
17
|
+
const results: ts.Node[] = [];
|
|
18
|
+
function visit(node: ts.Node): void {
|
|
19
|
+
if (predicate(node)) {
|
|
20
|
+
results.push(node);
|
|
21
|
+
}
|
|
22
|
+
ts.forEachChild(node, visit);
|
|
23
|
+
}
|
|
24
|
+
visit(root);
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Built-in selector: matches `new X(...)` constructor calls (resource instantiations).
|
|
30
|
+
*/
|
|
31
|
+
function selectResource(sf: ts.SourceFile): ts.Node[] {
|
|
32
|
+
return collectNodes(sf, (node) => ts.isNewExpression(node));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Built-in selector: matches any resource — alias for `resource`.
|
|
37
|
+
*/
|
|
38
|
+
function selectAnyResource(sf: ts.SourceFile): ts.Node[] {
|
|
39
|
+
return selectResource(sf);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Built-in selector: matches string literal nodes.
|
|
44
|
+
*/
|
|
45
|
+
function selectStringLiteral(sf: ts.SourceFile): ts.Node[] {
|
|
46
|
+
return collectNodes(sf, (node) => ts.isStringLiteral(node));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Built-in selector: matches exported declaration names.
|
|
51
|
+
*/
|
|
52
|
+
function selectExportName(sf: ts.SourceFile): ts.Node[] {
|
|
53
|
+
return collectNodes(sf, (node) => {
|
|
54
|
+
if (ts.isVariableStatement(node)) {
|
|
55
|
+
return node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
56
|
+
}
|
|
57
|
+
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
|
|
58
|
+
return node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Built-in selector: matches import source strings.
|
|
66
|
+
*/
|
|
67
|
+
function selectImportSource(sf: ts.SourceFile): ts.Node[] {
|
|
68
|
+
return collectNodes(sf, (node) => {
|
|
69
|
+
if (ts.isImportDeclaration(node)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Built-in selector: matches property assignments.
|
|
78
|
+
*/
|
|
79
|
+
function selectProperty(sf: ts.SourceFile): ts.Node[] {
|
|
80
|
+
return collectNodes(sf, (node) => ts.isPropertyAssignment(node));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Built-in selector: matches the type argument of `new X<Type>(...)` expressions.
|
|
85
|
+
*/
|
|
86
|
+
function selectResourceType(sf: ts.SourceFile): ts.Node[] {
|
|
87
|
+
return collectNodes(sf, (node) => {
|
|
88
|
+
if (ts.isNewExpression(node) && node.typeArguments && node.typeArguments.length > 0) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Built-in selector: matches exported const declarations.
|
|
97
|
+
*/
|
|
98
|
+
function selectExportedConst(sf: ts.SourceFile): ts.Node[] {
|
|
99
|
+
return collectNodes(sf, (node) => {
|
|
100
|
+
if (!ts.isVariableStatement(node)) return false;
|
|
101
|
+
const hasExport = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
102
|
+
if (!hasExport) return false;
|
|
103
|
+
return node.declarationList.flags === ts.NodeFlags.Const ||
|
|
104
|
+
(node.declarationList.flags & ts.NodeFlags.Const) !== 0;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Register built-in selectors
|
|
109
|
+
const builtins: [string, SelectorFn][] = [
|
|
110
|
+
["resource", selectResource],
|
|
111
|
+
["any-resource", selectAnyResource],
|
|
112
|
+
["string-literal", selectStringLiteral],
|
|
113
|
+
["export-name", selectExportName],
|
|
114
|
+
["import-source", selectImportSource],
|
|
115
|
+
["property", selectProperty],
|
|
116
|
+
["resource-type", selectResourceType],
|
|
117
|
+
["exported-const", selectExportedConst],
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
for (const [name, fn] of builtins) {
|
|
121
|
+
selectorRegistry.set(name, fn);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Register a custom selector.
|
|
126
|
+
*/
|
|
127
|
+
export function registerSelector(name: string, fn: SelectorFn): void {
|
|
128
|
+
selectorRegistry.set(name, fn);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve a selector name (or compound expression) to a SelectorFn.
|
|
133
|
+
*
|
|
134
|
+
* Compound selectors use `>` to scope: `"resource > property"` means
|
|
135
|
+
* "find all `resource` nodes, then within each, find all `property` nodes".
|
|
136
|
+
*/
|
|
137
|
+
export function resolveSelector(name: string): SelectorFn {
|
|
138
|
+
// Check for compound selector
|
|
139
|
+
if (name.includes(">")) {
|
|
140
|
+
const parts = name.split(">").map((s) => s.trim());
|
|
141
|
+
const fns = parts.map((part) => {
|
|
142
|
+
const fn = selectorRegistry.get(part);
|
|
143
|
+
if (!fn) {
|
|
144
|
+
throw new Error(`Unknown selector: "${part}"`);
|
|
145
|
+
}
|
|
146
|
+
return fn;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return (sf: ts.SourceFile): ts.Node[] => {
|
|
150
|
+
// Start with results from first selector
|
|
151
|
+
let nodes = fns[0](sf);
|
|
152
|
+
|
|
153
|
+
// For each subsequent selector, search within current nodes
|
|
154
|
+
for (let i = 1; i < fns.length; i++) {
|
|
155
|
+
const nextNodes: ts.Node[] = [];
|
|
156
|
+
for (const parent of nodes) {
|
|
157
|
+
const childSelector = fns[i];
|
|
158
|
+
const allMatches = childSelector(sf);
|
|
159
|
+
for (const match of allMatches) {
|
|
160
|
+
if (isDescendantOf(match, parent)) {
|
|
161
|
+
nextNodes.push(match);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
nodes = nextNodes;
|
|
166
|
+
}
|
|
167
|
+
return nodes;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const fn = selectorRegistry.get(name);
|
|
172
|
+
if (!fn) {
|
|
173
|
+
throw new Error(`Unknown selector: "${name}"`);
|
|
174
|
+
}
|
|
175
|
+
return fn;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if a node is a descendant of another node.
|
|
180
|
+
*/
|
|
181
|
+
function isDescendantOf(node: ts.Node, ancestor: ts.Node): boolean {
|
|
182
|
+
let current = node.parent;
|
|
183
|
+
while (current) {
|
|
184
|
+
if (current === ancestor) return true;
|
|
185
|
+
current = current.parent;
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic lexicon-based LSP completion and hover providers.
|
|
3
|
+
*
|
|
4
|
+
* Provides LexiconIndex for looking up resources/properties by class name,
|
|
5
|
+
* and helper functions that implement the common completion/hover patterns
|
|
6
|
+
* shared across lexicons.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "./types";
|
|
10
|
+
|
|
11
|
+
// ── Lexicon Entry ──────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface LexiconEntry {
|
|
14
|
+
resourceType: string;
|
|
15
|
+
kind: "resource" | "property";
|
|
16
|
+
lexicon: string;
|
|
17
|
+
attrs?: Record<string, string>;
|
|
18
|
+
propertyConstraints?: Record<string, unknown>;
|
|
19
|
+
createOnly?: string[];
|
|
20
|
+
writeOnly?: string[];
|
|
21
|
+
primaryIdentifier?: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── LexiconIndex ───────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Indexed access to lexicon entries for LSP features.
|
|
28
|
+
*/
|
|
29
|
+
export class LexiconIndex {
|
|
30
|
+
constructor(private entries: Record<string, LexiconEntry>) {}
|
|
31
|
+
|
|
32
|
+
/** All resource class names with their backing type names. */
|
|
33
|
+
getResourceNames(): Array<{ className: string; resourceType: string }> {
|
|
34
|
+
const results: Array<{ className: string; resourceType: string }> = [];
|
|
35
|
+
for (const [name, entry] of Object.entries(this.entries)) {
|
|
36
|
+
if (entry.kind === "resource") {
|
|
37
|
+
results.push({ className: name, resourceType: entry.resourceType });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Property names available on a given resource class. */
|
|
44
|
+
getPropertyNames(className: string): string[] {
|
|
45
|
+
const entry = this.entries[className];
|
|
46
|
+
if (!entry || entry.kind !== "resource") return [];
|
|
47
|
+
|
|
48
|
+
const props: string[] = [];
|
|
49
|
+
if (entry.propertyConstraints) {
|
|
50
|
+
props.push(...Object.keys(entry.propertyConstraints));
|
|
51
|
+
}
|
|
52
|
+
if (entry.createOnly) {
|
|
53
|
+
for (const p of entry.createOnly) {
|
|
54
|
+
if (!props.includes(p)) props.push(p);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (entry.writeOnly) {
|
|
58
|
+
for (const p of entry.writeOnly) {
|
|
59
|
+
if (!props.includes(p)) props.push(p);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return props;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Look up a single entry by class name. */
|
|
66
|
+
getEntry(name: string): LexiconEntry | undefined {
|
|
67
|
+
return this.entries[name];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Completion helper ──────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Provide completions from a lexicon index.
|
|
75
|
+
*
|
|
76
|
+
* Handles two contexts:
|
|
77
|
+
* - After `new ` — suggests resource class names
|
|
78
|
+
* - Inside constructor props — suggests property names
|
|
79
|
+
*
|
|
80
|
+
* @param importSource - Human-readable source label for documentation
|
|
81
|
+
* (e.g. "AWS CloudFormation resource")
|
|
82
|
+
*/
|
|
83
|
+
export function lexiconCompletions(
|
|
84
|
+
ctx: CompletionContext,
|
|
85
|
+
index: LexiconIndex,
|
|
86
|
+
importSource: string,
|
|
87
|
+
): CompletionItem[] {
|
|
88
|
+
const { linePrefix, wordAtCursor } = ctx;
|
|
89
|
+
|
|
90
|
+
// After `new ` — suggest resource class names
|
|
91
|
+
if (/\bnew\s+\w*$/.test(linePrefix)) {
|
|
92
|
+
const resources = index.getResourceNames();
|
|
93
|
+
const filtered = wordAtCursor
|
|
94
|
+
? resources.filter((r) => r.className.toLowerCase().startsWith(wordAtCursor.toLowerCase()))
|
|
95
|
+
: resources;
|
|
96
|
+
|
|
97
|
+
return filtered.slice(0, 50).map((r) => ({
|
|
98
|
+
label: r.className,
|
|
99
|
+
insertText: r.className,
|
|
100
|
+
kind: "resource" as const,
|
|
101
|
+
detail: r.resourceType,
|
|
102
|
+
documentation: `${importSource}: ${r.resourceType}`,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Inside constructor props — look for `new ClassName({` pattern
|
|
107
|
+
const constructorMatch = ctx.content.slice(0, ctx.content.split("\n").slice(0, ctx.position.line + 1).join("\n").length)
|
|
108
|
+
.match(/\bnew\s+(\w+)\s*\(\s*(?:["'][^"']*["']\s*,\s*)?{[^}]*$/s);
|
|
109
|
+
|
|
110
|
+
if (constructorMatch) {
|
|
111
|
+
const className = constructorMatch[1];
|
|
112
|
+
const props = index.getPropertyNames(className);
|
|
113
|
+
if (props.length > 0) {
|
|
114
|
+
const filtered = wordAtCursor
|
|
115
|
+
? props.filter((p) => p.toLowerCase().startsWith(wordAtCursor.toLowerCase()))
|
|
116
|
+
: props;
|
|
117
|
+
return filtered.map((p) => ({
|
|
118
|
+
label: p,
|
|
119
|
+
insertText: p,
|
|
120
|
+
kind: "property" as const,
|
|
121
|
+
detail: `Property of ${className}`,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Hover helper ───────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Provide hover information from a lexicon index.
|
|
133
|
+
*
|
|
134
|
+
* @param formatHover - Lexicon-specific hover content formatter.
|
|
135
|
+
* If not provided, uses a generic format.
|
|
136
|
+
*/
|
|
137
|
+
export function lexiconHover(
|
|
138
|
+
ctx: HoverContext,
|
|
139
|
+
index: LexiconIndex,
|
|
140
|
+
formatHover?: (className: string, entry: LexiconEntry) => HoverInfo | undefined,
|
|
141
|
+
): HoverInfo | undefined {
|
|
142
|
+
const { word } = ctx;
|
|
143
|
+
if (!word) return undefined;
|
|
144
|
+
|
|
145
|
+
const entry = index.getEntry(word);
|
|
146
|
+
if (!entry) return undefined;
|
|
147
|
+
|
|
148
|
+
if (formatHover) {
|
|
149
|
+
return formatHover(word, entry);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Default hover format for resource entries
|
|
153
|
+
if (entry.kind === "resource") {
|
|
154
|
+
return defaultResourceHover(word, entry);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function defaultResourceHover(className: string, entry: LexiconEntry): HoverInfo {
|
|
161
|
+
const lines: string[] = [];
|
|
162
|
+
|
|
163
|
+
lines.push(`**${className}**`);
|
|
164
|
+
lines.push("");
|
|
165
|
+
lines.push(`Resource type: \`${entry.resourceType}\``);
|
|
166
|
+
|
|
167
|
+
if (entry.attrs && Object.keys(entry.attrs).length > 0) {
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("**Attributes:**");
|
|
170
|
+
for (const [key, value] of Object.entries(entry.attrs)) {
|
|
171
|
+
lines.push(`- \`${key}\` → \`${value}\``);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (entry.primaryIdentifier && entry.primaryIdentifier.length > 0) {
|
|
176
|
+
lines.push("");
|
|
177
|
+
lines.push(`**Primary identifier:** ${entry.primaryIdentifier.map((p) => `\`${p}\``).join(", ")}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (entry.createOnly && entry.createOnly.length > 0) {
|
|
181
|
+
lines.push("");
|
|
182
|
+
lines.push(`**Create-only:** ${entry.createOnly.map((p) => `\`${p}\``).join(", ")}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (entry.writeOnly && entry.writeOnly.length > 0) {
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push(`**Write-only:** ${entry.writeOnly.map((p) => `\`${p}\``).join(", ")}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { contents: lines.join("\n") };
|
|
191
|
+
}
|
package/src/lsp/types.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simplified LSP types for lexicon plugin contributions.
|
|
3
|
+
* No dependency on vscode-languageserver — just plain interfaces.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface Position {
|
|
7
|
+
line: number;
|
|
8
|
+
character: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Range {
|
|
12
|
+
start: Position;
|
|
13
|
+
end: Position;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TextEdit {
|
|
17
|
+
range: Range;
|
|
18
|
+
newText: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CompletionItemKind =
|
|
22
|
+
| "resource"
|
|
23
|
+
| "property"
|
|
24
|
+
| "intrinsic"
|
|
25
|
+
| "pseudo-parameter"
|
|
26
|
+
| "value";
|
|
27
|
+
|
|
28
|
+
export interface CompletionItem {
|
|
29
|
+
label: string;
|
|
30
|
+
insertText?: string;
|
|
31
|
+
kind?: CompletionItemKind;
|
|
32
|
+
detail?: string;
|
|
33
|
+
documentation?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CompletionContext {
|
|
37
|
+
uri: string;
|
|
38
|
+
content: string;
|
|
39
|
+
position: Position;
|
|
40
|
+
wordAtCursor: string;
|
|
41
|
+
linePrefix: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface HoverInfo {
|
|
45
|
+
contents: string;
|
|
46
|
+
range?: Range;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface HoverContext {
|
|
50
|
+
uri: string;
|
|
51
|
+
content: string;
|
|
52
|
+
position: Position;
|
|
53
|
+
word: string;
|
|
54
|
+
lineText: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type CodeActionKind = "quickfix" | "refactor" | "source";
|
|
58
|
+
|
|
59
|
+
export interface CodeAction {
|
|
60
|
+
title: string;
|
|
61
|
+
kind: CodeActionKind;
|
|
62
|
+
edits?: TextEdit[];
|
|
63
|
+
diagnosticRuleId?: string;
|
|
64
|
+
isPreferred?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CodeActionContext {
|
|
68
|
+
uri: string;
|
|
69
|
+
content: string;
|
|
70
|
+
range: Range;
|
|
71
|
+
diagnostics: CodeActionDiagnostic[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CodeActionDiagnostic {
|
|
75
|
+
range: Range;
|
|
76
|
+
message: string;
|
|
77
|
+
ruleId?: string;
|
|
78
|
+
severity?: "error" | "warning" | "info";
|
|
79
|
+
}
|
package/src/mcp/types.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP contribution types for lexicon plugins.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface McpToolContribution {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object";
|
|
10
|
+
properties: Record<string, unknown>;
|
|
11
|
+
required?: string[];
|
|
12
|
+
};
|
|
13
|
+
handler: (params: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface McpResourceContribution {
|
|
17
|
+
uri: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
mimeType?: string;
|
|
21
|
+
handler: () => Promise<string>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { scanProject } from "./scan";
|
|
3
|
+
import { withTestDir } from "@intentius/chant-test-utils";
|
|
4
|
+
import { writeFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
describe("scanProject", () => {
|
|
8
|
+
test("finds barrel and extracts lexicon package", async () => {
|
|
9
|
+
await withTestDir(async (dir) => {
|
|
10
|
+
await writeFile(
|
|
11
|
+
join(dir, "_.ts"),
|
|
12
|
+
`export * from "@intentius/chant-lexicon-testdom";\nexport * from "./storage";\n`,
|
|
13
|
+
);
|
|
14
|
+
await writeFile(
|
|
15
|
+
join(dir, "storage.ts"),
|
|
16
|
+
`import * as td from "@intentius/chant-lexicon-testdom";\nexport const bucket = new td.Bucket({});\n`,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const scan = scanProject(dir);
|
|
20
|
+
expect(scan.barrelPath).toBe(join(dir, "_.ts"));
|
|
21
|
+
expect(scan.lexiconPackage).toBe("@intentius/chant-lexicon-testdom");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("collects exports from sibling files", async () => {
|
|
26
|
+
await withTestDir(async (dir) => {
|
|
27
|
+
await writeFile(
|
|
28
|
+
join(dir, "_.ts"),
|
|
29
|
+
`export * from "@intentius/chant-lexicon-testdom";\n`,
|
|
30
|
+
);
|
|
31
|
+
await writeFile(
|
|
32
|
+
join(dir, "storage.ts"),
|
|
33
|
+
`import * as td from "@intentius/chant-lexicon-testdom";\nexport const dataBucket = new td.Bucket({});\nexport const logsBucket = new td.Bucket({});\n`,
|
|
34
|
+
);
|
|
35
|
+
await writeFile(
|
|
36
|
+
join(dir, "compute.ts"),
|
|
37
|
+
`import * as td from "@intentius/chant-lexicon-testdom";\nexport const handler = new td.Function({});\n`,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const scan = scanProject(dir);
|
|
41
|
+
expect(scan.exports).toHaveLength(3);
|
|
42
|
+
const names = scan.exports.map((e) => e.name);
|
|
43
|
+
expect(names).toContain("dataBucket");
|
|
44
|
+
expect(names).toContain("logsBucket");
|
|
45
|
+
expect(names).toContain("handler");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("infers class names from new X.ClassName(...) patterns", async () => {
|
|
50
|
+
await withTestDir(async (dir) => {
|
|
51
|
+
await writeFile(
|
|
52
|
+
join(dir, "_.ts"),
|
|
53
|
+
`export * from "@intentius/chant-lexicon-testdom";\n`,
|
|
54
|
+
);
|
|
55
|
+
await writeFile(
|
|
56
|
+
join(dir, "resources.ts"),
|
|
57
|
+
[
|
|
58
|
+
`import * as td from "@intentius/chant-lexicon-testdom";`,
|
|
59
|
+
`export const bucket = new td.Bucket({});`,
|
|
60
|
+
`export const role = new td.Role({});`,
|
|
61
|
+
`export const fn = new td.Function({});`,
|
|
62
|
+
].join("\n"),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const scan = scanProject(dir);
|
|
66
|
+
expect(scan.exports).toHaveLength(3);
|
|
67
|
+
|
|
68
|
+
const bucket = scan.exports.find((e) => e.name === "bucket");
|
|
69
|
+
expect(bucket?.className).toBe("Bucket");
|
|
70
|
+
|
|
71
|
+
const role = scan.exports.find((e) => e.name === "role");
|
|
72
|
+
expect(role?.className).toBe("Role");
|
|
73
|
+
|
|
74
|
+
const fn = scan.exports.find((e) => e.name === "fn");
|
|
75
|
+
expect(fn?.className).toBe("Function");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("infers class names from type annotations", async () => {
|
|
80
|
+
await withTestDir(async (dir) => {
|
|
81
|
+
await writeFile(
|
|
82
|
+
join(dir, "_.ts"),
|
|
83
|
+
`export * from "@intentius/chant-lexicon-testdom";\n`,
|
|
84
|
+
);
|
|
85
|
+
await writeFile(
|
|
86
|
+
join(dir, "config.ts"),
|
|
87
|
+
[
|
|
88
|
+
`import * as td from "@intentius/chant-lexicon-testdom";`,
|
|
89
|
+
`export const encryption: td.ServerSideEncryptionByDefault = { sseAlgorithm: "AES256" };`,
|
|
90
|
+
`export const versioning: td.VersioningConfiguration = { status: "Enabled" };`,
|
|
91
|
+
].join("\n"),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const scan = scanProject(dir);
|
|
95
|
+
expect(scan.exports).toHaveLength(2);
|
|
96
|
+
|
|
97
|
+
const encryption = scan.exports.find((e) => e.name === "encryption");
|
|
98
|
+
expect(encryption?.className).toBe("ServerSideEncryptionByDefault");
|
|
99
|
+
|
|
100
|
+
const versioning = scan.exports.find((e) => e.name === "versioning");
|
|
101
|
+
expect(versioning?.className).toBe("VersioningConfiguration");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("excludes barrel, test files, and underscore-prefixed files", async () => {
|
|
106
|
+
await withTestDir(async (dir) => {
|
|
107
|
+
await writeFile(
|
|
108
|
+
join(dir, "_.ts"),
|
|
109
|
+
`export * from "@intentius/chant-lexicon-testdom";\n`,
|
|
110
|
+
);
|
|
111
|
+
await writeFile(
|
|
112
|
+
join(dir, "_internal.ts"),
|
|
113
|
+
`export const secret = "hidden";\n`,
|
|
114
|
+
);
|
|
115
|
+
await writeFile(
|
|
116
|
+
join(dir, "app.test.ts"),
|
|
117
|
+
`import { test } from "bun:test";\ntest("passes", () => {});\n`,
|
|
118
|
+
);
|
|
119
|
+
await writeFile(
|
|
120
|
+
join(dir, "app.spec.ts"),
|
|
121
|
+
`import { test } from "bun:test";\ntest("passes", () => {});\n`,
|
|
122
|
+
);
|
|
123
|
+
await writeFile(
|
|
124
|
+
join(dir, "app.ts"),
|
|
125
|
+
`export const app = "hello";\n`,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const scan = scanProject(dir);
|
|
129
|
+
expect(scan.exports).toHaveLength(1);
|
|
130
|
+
expect(scan.exports[0].name).toBe("app");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("throws on missing barrel", async () => {
|
|
135
|
+
await withTestDir(async (dir) => {
|
|
136
|
+
expect(() => scanProject(dir)).toThrow("No barrel file found");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("returns empty className for exports without recognizable patterns", async () => {
|
|
141
|
+
await withTestDir(async (dir) => {
|
|
142
|
+
await writeFile(
|
|
143
|
+
join(dir, "_.ts"),
|
|
144
|
+
`export * from "@intentius/chant-lexicon-testdom";\n`,
|
|
145
|
+
);
|
|
146
|
+
await writeFile(
|
|
147
|
+
join(dir, "utils.ts"),
|
|
148
|
+
`export const greeting = "hello";\nexport const count = 42;\n`,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const scan = scanProject(dir);
|
|
152
|
+
expect(scan.exports).toHaveLength(2);
|
|
153
|
+
for (const exp of scan.exports) {
|
|
154
|
+
expect(exp.className).toBe("");
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("infers class names from new _.ClassName(...) patterns", async () => {
|
|
160
|
+
await withTestDir(async (dir) => {
|
|
161
|
+
await writeFile(
|
|
162
|
+
join(dir, "_.ts"),
|
|
163
|
+
`export * from "@intentius/chant-lexicon-testdom";\n`,
|
|
164
|
+
);
|
|
165
|
+
await writeFile(
|
|
166
|
+
join(dir, "resources.ts"),
|
|
167
|
+
[
|
|
168
|
+
`import * as _ from "./_";`,
|
|
169
|
+
`export const bucket = new _.Bucket({});`,
|
|
170
|
+
].join("\n"),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const scan = scanProject(dir);
|
|
174
|
+
expect(scan.exports).toHaveLength(1);
|
|
175
|
+
expect(scan.exports[0].className).toBe("Bucket");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|