lua-cli 3.12.1 → 3.13.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/dist/api/logs.api.service.d.ts +6 -3
- package/dist/api/logs.api.service.d.ts.map +1 -1
- package/dist/api/logs.api.service.js +5 -4
- package/dist/api/logs.api.service.js.map +1 -1
- package/dist/cli/command-definitions.d.ts.map +1 -1
- package/dist/cli/command-definitions.js +24 -2
- package/dist/cli/command-definitions.js.map +1 -1
- package/dist/commands/channels.d.ts.map +1 -1
- package/dist/commands/channels.js +2 -11
- package/dist/commands/channels.js.map +1 -1
- package/dist/commands/chat-log-probe.d.ts +59 -0
- package/dist/commands/chat-log-probe.d.ts.map +1 -0
- package/dist/commands/chat-log-probe.js +91 -0
- package/dist/commands/chat-log-probe.js.map +1 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +134 -28
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/compile.d.ts +7 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +32 -2
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +7 -8
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +108 -23
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/devices.d.ts.map +1 -1
- package/dist/commands/devices.js +3 -2
- package/dist/commands/devices.js.map +1 -1
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +43 -20
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/features.d.ts.map +1 -1
- package/dist/commands/features.js +2 -12
- package/dist/commands/features.js.map +1 -1
- package/dist/commands/governance.d.ts.map +1 -1
- package/dist/commands/governance.js +4 -8
- package/dist/commands/governance.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/integrations.d.ts.map +1 -1
- package/dist/commands/integrations.js +4 -2
- package/dist/commands/integrations.js.map +1 -1
- package/dist/commands/jobs.d.ts.map +1 -1
- package/dist/commands/jobs.js +61 -23
- package/dist/commands/jobs.js.map +1 -1
- package/dist/commands/log-tip.d.ts +79 -0
- package/dist/commands/log-tip.d.ts.map +1 -0
- package/dist/commands/log-tip.js +91 -0
- package/dist/commands/log-tip.js.map +1 -0
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +97 -136
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/marketplace.d.ts.map +1 -1
- package/dist/commands/marketplace.js +14 -25
- package/dist/commands/marketplace.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +17 -19
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/models.d.ts.map +1 -1
- package/dist/commands/models.js +2 -1
- package/dist/commands/models.js.map +1 -1
- package/dist/commands/persona.d.ts.map +1 -1
- package/dist/commands/persona.js +4 -30
- package/dist/commands/persona.js.map +1 -1
- package/dist/commands/postprocessors.d.ts.map +1 -1
- package/dist/commands/postprocessors.js +43 -17
- package/dist/commands/postprocessors.js.map +1 -1
- package/dist/commands/preprocessors.d.ts.map +1 -1
- package/dist/commands/preprocessors.js +43 -17
- package/dist/commands/preprocessors.js.map +1 -1
- package/dist/commands/production.d.ts.map +1 -1
- package/dist/commands/production.js +2 -12
- package/dist/commands/production.js.map +1 -1
- package/dist/commands/push-helpers.d.ts +42 -0
- package/dist/commands/push-helpers.d.ts.map +1 -0
- package/dist/commands/push-helpers.js +48 -0
- package/dist/commands/push-helpers.js.map +1 -0
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +269 -54
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/pushBackup.d.ts.map +1 -1
- package/dist/commands/pushBackup.js +7 -1
- package/dist/commands/pushBackup.js.map +1 -1
- package/dist/commands/resources.d.ts.map +1 -1
- package/dist/commands/resources.js +2 -12
- package/dist/commands/resources.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +51 -36
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/status.d.ts +14 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +734 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +37 -2
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/telemetry.d.ts.map +1 -1
- package/dist/commands/telemetry.js +4 -0
- package/dist/commands/telemetry.js.map +1 -1
- package/dist/commands/test-output.helpers.d.ts +24 -0
- package/dist/commands/test-output.helpers.d.ts.map +1 -0
- package/dist/commands/test-output.helpers.js +62 -0
- package/dist/commands/test-output.helpers.js.map +1 -0
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +236 -76
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/webhooks.d.ts.map +1 -1
- package/dist/commands/webhooks.js +34 -21
- package/dist/commands/webhooks.js.map +1 -1
- package/dist/compiler/agent-traverser.d.ts +50 -14
- package/dist/compiler/agent-traverser.d.ts.map +1 -1
- package/dist/compiler/agent-traverser.js +191 -86
- package/dist/compiler/agent-traverser.js.map +1 -1
- package/dist/compiler/compiler.d.ts +32 -0
- package/dist/compiler/compiler.d.ts.map +1 -1
- package/dist/compiler/compiler.js +143 -18
- package/dist/compiler/compiler.js.map +1 -1
- package/dist/compiler/index.d.ts +1 -1
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/compiler/index.js +1 -1
- package/dist/compiler/index.js.map +1 -1
- package/dist/compiler/plugins/base.d.ts +21 -0
- package/dist/compiler/plugins/base.d.ts.map +1 -1
- package/dist/compiler/plugins/base.js +10 -0
- package/dist/compiler/plugins/base.js.map +1 -1
- package/dist/compiler/plugins/skill.plugin.d.ts +35 -9
- package/dist/compiler/plugins/skill.plugin.d.ts.map +1 -1
- package/dist/compiler/plugins/skill.plugin.js +131 -84
- package/dist/compiler/plugins/skill.plugin.js.map +1 -1
- package/dist/compiler/types.d.ts +33 -0
- package/dist/compiler/types.d.ts.map +1 -1
- package/dist/compiler/types.js.map +1 -1
- package/dist/compiler/utils/ast-helpers.d.ts +1 -16
- package/dist/compiler/utils/ast-helpers.d.ts.map +1 -1
- package/dist/compiler/utils/ast-helpers.js +2 -31
- package/dist/compiler/utils/ast-helpers.js.map +1 -1
- package/dist/compiler/utils/common.d.ts +14 -0
- package/dist/compiler/utils/common.d.ts.map +1 -1
- package/dist/compiler/utils/common.js +38 -0
- package/dist/compiler/utils/common.js.map +1 -1
- package/dist/compiler/utils/path-resolver.d.ts +67 -10
- package/dist/compiler/utils/path-resolver.d.ts.map +1 -1
- package/dist/compiler/utils/path-resolver.js +120 -24
- package/dist/compiler/utils/path-resolver.js.map +1 -1
- package/dist/compiler/utils/reference-resolver.d.ts +196 -0
- package/dist/compiler/utils/reference-resolver.d.ts.map +1 -0
- package/dist/compiler/utils/reference-resolver.js +1030 -0
- package/dist/compiler/utils/reference-resolver.js.map +1 -0
- package/dist/compiler/utils/schema-converter.d.ts +25 -0
- package/dist/compiler/utils/schema-converter.d.ts.map +1 -1
- package/dist/compiler/utils/schema-converter.js +83 -4
- package/dist/compiler/utils/schema-converter.js.map +1 -1
- package/dist/compiler/utils/workspace.d.ts +35 -0
- package/dist/compiler/utils/workspace.d.ts.map +1 -0
- package/dist/compiler/utils/workspace.js +121 -0
- package/dist/compiler/utils/workspace.js.map +1 -0
- package/dist/compiler/utils/zod-runtime.mjs +5334 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/backup.d.ts +19 -1
- package/dist/interfaces/backup.d.ts.map +1 -1
- package/dist/interfaces/chat.d.ts +2 -2
- package/dist/interfaces/chat.d.ts.map +1 -1
- package/dist/interfaces/logs.d.ts +7 -4
- package/dist/interfaces/logs.d.ts.map +1 -1
- package/dist/primitives/base.handler.d.ts +10 -0
- package/dist/primitives/base.handler.d.ts.map +1 -1
- package/dist/primitives/base.handler.js +73 -44
- package/dist/primitives/base.handler.js.map +1 -1
- package/dist/primitives/job.handler.d.ts +5 -0
- package/dist/primitives/job.handler.d.ts.map +1 -1
- package/dist/primitives/job.handler.js +9 -0
- package/dist/primitives/job.handler.js.map +1 -1
- package/dist/primitives/webhook.handler.d.ts +2 -1
- package/dist/primitives/webhook.handler.d.ts.map +1 -1
- package/dist/primitives/webhook.handler.js +7 -1
- package/dist/primitives/webhook.handler.js.map +1 -1
- package/dist/services/auth.d.ts +8 -0
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +19 -0
- package/dist/services/auth.js.map +1 -1
- package/dist/services/sandbox.service.d.ts.map +1 -1
- package/dist/services/sandbox.service.js +6 -2
- package/dist/services/sandbox.service.js.map +1 -1
- package/dist/utils/aliases.d.ts +7 -0
- package/dist/utils/aliases.d.ts.map +1 -0
- package/dist/utils/aliases.js +457 -0
- package/dist/utils/aliases.js.map +1 -0
- package/dist/utils/backup-helpers.d.ts +24 -0
- package/dist/utils/backup-helpers.d.ts.map +1 -1
- package/dist/utils/backup-helpers.js +94 -8
- package/dist/utils/backup-helpers.js.map +1 -1
- package/dist/utils/chat-attachments.d.ts +15 -0
- package/dist/utils/chat-attachments.d.ts.map +1 -0
- package/dist/utils/chat-attachments.js +116 -0
- package/dist/utils/chat-attachments.js.map +1 -0
- package/dist/utils/cli.d.ts +24 -6
- package/dist/utils/cli.d.ts.map +1 -1
- package/dist/utils/cli.js +23 -9
- package/dist/utils/cli.js.map +1 -1
- package/dist/utils/hints.d.ts +81 -0
- package/dist/utils/hints.d.ts.map +1 -0
- package/dist/utils/hints.js +83 -0
- package/dist/utils/hints.js.map +1 -0
- package/dist/utils/suggest.d.ts +3 -0
- package/dist/utils/suggest.d.ts.map +1 -0
- package/dist/utils/suggest.js +34 -0
- package/dist/utils/suggest.js.map +1 -0
- package/dist/utils/write-info.d.ts +9 -0
- package/dist/utils/write-info.d.ts.map +1 -0
- package/dist/utils/write-info.js +11 -0
- package/dist/utils/write-info.js.map +1 -0
- package/docs/README.md +1 -1
- package/node_modules/@lua/shared-types/dist/index.d.mts +64 -2
- package/node_modules/@lua/shared-types/dist/index.d.ts +64 -2
- package/node_modules/@lua/shared-types/dist/index.js +27 -0
- package/node_modules/@lua/shared-types/dist/index.mjs +24 -0
- package/node_modules/@lua/shared-types/src/channel.type.ts +1 -0
- package/node_modules/@lua/shared-types/src/client-tools.types.ts +47 -0
- package/node_modules/@lua/shared-types/src/index.ts +2 -0
- package/node_modules/@lua/shared-types/src/vm-execution-log.types.ts +41 -0
- package/package.json +10 -6
- package/scripts/build-vendor.mjs +53 -0
- package/scripts/vendor-entry.mjs +11 -0
- package/template/examples/skills/tools/CustomDataTool.ts +1 -5
- package/template/examples/skills/tools/PremiumFeatureTool.ts +18 -18
- package/template/package.json +1 -1
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitive Reference Resolver
|
|
3
|
+
*
|
|
4
|
+
* Statically resolves a TypeScript expression that is expected to yield an
|
|
5
|
+
* array of primitive instances (e.g. `mcpServers: [luaDocsMcpServer, ...more]`)
|
|
6
|
+
* into a list of named references (`ResolvedRef[]`), without executing user
|
|
7
|
+
* code.
|
|
8
|
+
*
|
|
9
|
+
* ## Supported expression shapes
|
|
10
|
+
*
|
|
11
|
+
* - Inline: `[new LuaMCPServer({...}), foo]`
|
|
12
|
+
* - Variable ref: `const servers = [...]; mcpServers: servers`
|
|
13
|
+
* - Spread: `[...base, new LuaMCPServer({...})]`
|
|
14
|
+
* - Cross-file: `import servers from './servers'; mcpServers: servers`
|
|
15
|
+
* - Re-export: `export { servers } from './impl'`
|
|
16
|
+
* - Barrel chains: `export * from './servers'` + same-name imports
|
|
17
|
+
* - Path aliases: `@shared/tools`, `~/lib/tools`, etc. (via tsconfig paths)
|
|
18
|
+
* - Property read: `const all = { mcp: [...] }; mcpServers: all.mcp`
|
|
19
|
+
* - Identifier chains: `const a = inner; const b = a; mcpServers: b`
|
|
20
|
+
*
|
|
21
|
+
* ## Result shape (Babel's `path.evaluate()` convention)
|
|
22
|
+
*
|
|
23
|
+
* { confident: true; value: ResolvedRef[] }
|
|
24
|
+
* | { confident: false; value: ResolvedRef[]; deopt: Node; reason: string;
|
|
25
|
+
* ruleId: 'lua/unresolved-primitive-ref' }
|
|
26
|
+
*
|
|
27
|
+
* The `deopt` field pinpoints the AST node that defeated resolution so the
|
|
28
|
+
* compiler can emit an actionable `file:line:col` diagnostic. Partial results
|
|
29
|
+
* (`value`) are returned even on deopt so the compiler can emit whatever was
|
|
30
|
+
* resolvable up to the unknown shape.
|
|
31
|
+
*
|
|
32
|
+
* ## Resolution strategy
|
|
33
|
+
*
|
|
34
|
+
* 1. **AST fast path** (primary). Traverse ArrayLiteralExpression, follow
|
|
35
|
+
* Identifiers to their VariableDeclaration (same-file or via ImportDeclaration
|
|
36
|
+
* → getModuleSpecifierSourceFile()), recursively expand spreads. This is
|
|
37
|
+
* the idiomatic ts-morph pattern (see ts-morph issue #644 — manual iteration
|
|
38
|
+
* is ~1000x faster than getExportedDeclarations() because it doesn't trigger
|
|
39
|
+
* full symbol binding). Files are added to the project on demand via
|
|
40
|
+
* `addSourceFileAtPath()` as the walk discovers them.
|
|
41
|
+
* 2. **Symbol fallback** (only when fast path exhausts). Uses
|
|
42
|
+
* `getAliasedSymbol()` + `symbol.getDeclarations()[0]` to handle exotic
|
|
43
|
+
* re-export chains the manual walk can't follow.
|
|
44
|
+
*
|
|
45
|
+
* ## Escape hatch
|
|
46
|
+
*
|
|
47
|
+
* A leading `/* @lua-ignore-ref *\/` comment on the expression silences the
|
|
48
|
+
* deopt diagnostic for users who intentionally construct the array dynamically.
|
|
49
|
+
*
|
|
50
|
+
* ## Why we don't evaluate code
|
|
51
|
+
*
|
|
52
|
+
* Earlier iterations used `ts-evaluator` to sandbox-evaluate array expressions.
|
|
53
|
+
* That path loses the link to the compile-time identifier (it yields runtime
|
|
54
|
+
* `.name` strings, not the class/variable name needed for import resolution)
|
|
55
|
+
* and adds a sandboxed-RCE surface (cf. CVE-2023-45133 in Babel). The AST
|
|
56
|
+
* approach covers every case the evaluator did, cheaper and safer.
|
|
57
|
+
*/
|
|
58
|
+
import { Node, } from 'ts-morph';
|
|
59
|
+
import { resolveModuleSpecifier, getProjectRootDir, } from './path-resolver.js';
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// PUBLIC API
|
|
62
|
+
// =============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Resolve an expression node that represents an array of primitive references.
|
|
65
|
+
*
|
|
66
|
+
* Typical usage from a plugin/traverser:
|
|
67
|
+
*
|
|
68
|
+
* const prop = configObj.getProperty('mcpServers');
|
|
69
|
+
* const init = (prop as PropertyAssignment).getInitializer();
|
|
70
|
+
* const result = resolveArrayRefs(init, { project });
|
|
71
|
+
* if (!result.confident) { log.warn(reportDeopt(result, 'mcpServers')); }
|
|
72
|
+
* const refs = result.value;
|
|
73
|
+
*
|
|
74
|
+
* @param expression - The expression node whose value is expected to be an
|
|
75
|
+
* array of primitive instances. Typically the initializer of a
|
|
76
|
+
* `skills` / `tools` / `mcpServers` property.
|
|
77
|
+
* @param ctx - Resolver context carrying the project and optional logger.
|
|
78
|
+
* @returns An `EvaluationResult` — confident + full value on success,
|
|
79
|
+
* confident:false + partial value + deopt node on unknown shapes.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveArrayRefs(expression, ctx) {
|
|
82
|
+
if (!expression) {
|
|
83
|
+
// A missing expression is not a user error (absent property); confident empty.
|
|
84
|
+
return { confident: true, value: [] };
|
|
85
|
+
}
|
|
86
|
+
// Honor the escape hatch: any `/* @lua-ignore-ref */` comment on or
|
|
87
|
+
// leading into this expression means "trust me, don't diagnose this".
|
|
88
|
+
if (hasIgnoreRefPragma(expression)) {
|
|
89
|
+
ctx.log?.(' [@lua-ignore-ref] skipping resolution by user request');
|
|
90
|
+
return { confident: true, value: [] };
|
|
91
|
+
}
|
|
92
|
+
// Derive rootDir: explicit ctx wins, then the Compiler-registered
|
|
93
|
+
// value on the Project, then process.cwd() as a last-resort fallback
|
|
94
|
+
// (matches pre-refactor behavior for ad-hoc test callers).
|
|
95
|
+
const rootDir = ctx.rootDir ??
|
|
96
|
+
getProjectRootDir(ctx.project) ??
|
|
97
|
+
process.cwd();
|
|
98
|
+
const state = {
|
|
99
|
+
project: ctx.project,
|
|
100
|
+
rootDir,
|
|
101
|
+
visited: ctx.visited ?? new Set(),
|
|
102
|
+
log: ctx.log ?? (() => { }),
|
|
103
|
+
slowPathHits: 0,
|
|
104
|
+
};
|
|
105
|
+
const value = [];
|
|
106
|
+
resolveArrayExpression(expression, value, state);
|
|
107
|
+
// Slow-path telemetry — opt-in via SMOKE_DEBUG. If this is ever >0 for
|
|
108
|
+
// a fixture that the resolver should handle via the fast path, that's
|
|
109
|
+
// a sign the fast path regressed or the shape is unexpected.
|
|
110
|
+
if (state.slowPathHits > 0 && process.env.SMOKE_DEBUG) {
|
|
111
|
+
state.log(`[resolver] followSymbolInFile slow-path hits: ${state.slowPathHits}`);
|
|
112
|
+
}
|
|
113
|
+
if (state.deopt) {
|
|
114
|
+
return {
|
|
115
|
+
confident: false,
|
|
116
|
+
value,
|
|
117
|
+
deopt: state.deopt.node,
|
|
118
|
+
reason: state.deopt.reason,
|
|
119
|
+
ruleId: 'lua/unresolved-primitive-ref',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { confident: true, value };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Format an `EvaluationResult.deopt` as a SARIF-style diagnostic string
|
|
126
|
+
* for `log.warn()`. Includes the rule id, file:line:col, reason, and a
|
|
127
|
+
* remediation hint pointing the user at the escape hatch.
|
|
128
|
+
*
|
|
129
|
+
* Call sites log this rather than constructing the string inline so every
|
|
130
|
+
* resolver deopt surfaces with the same shape.
|
|
131
|
+
*/
|
|
132
|
+
export function reportDeopt(result, propertyName) {
|
|
133
|
+
const deopt = result.deopt;
|
|
134
|
+
const sourceFile = deopt.getSourceFile();
|
|
135
|
+
const filePath = sourceFile.getFilePath();
|
|
136
|
+
const { line, column } = sourceFile.getLineAndColumnAtPos(deopt.getStart());
|
|
137
|
+
const snippet = truncate(deopt.getText(), 60);
|
|
138
|
+
return (`[${result.ruleId}] ${filePath}:${line}:${column}\n` +
|
|
139
|
+
` could not statically resolve "${propertyName}": ${result.reason}\n` +
|
|
140
|
+
` at: \`${snippet}\`\n` +
|
|
141
|
+
` hint: inline the array, export the value as a plain array literal, ` +
|
|
142
|
+
`or add a leading /* @lua-ignore-ref */ comment to silence`);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Format a SARIF-style diagnostic that isn't anchored on an AST node we
|
|
146
|
+
* parsed directly (e.g. "tried to find a tool in this file and the
|
|
147
|
+
* plugin returned nothing"). For node-anchored deopts, use
|
|
148
|
+
* `reportDeopt()` instead — it produces richer output.
|
|
149
|
+
*
|
|
150
|
+
* All resolver/traverser warnings should flow through either this
|
|
151
|
+
* helper or `reportDeopt`, so every line the user sees has a rule id
|
|
152
|
+
* and follows the same shape.
|
|
153
|
+
*/
|
|
154
|
+
export function formatSarifWarning(args) {
|
|
155
|
+
const loc = args.line !== undefined && args.column !== undefined
|
|
156
|
+
? `${args.filePath}:${args.line}:${args.column}`
|
|
157
|
+
: args.filePath;
|
|
158
|
+
const hintLine = args.hint ? `\n hint: ${args.hint}` : '';
|
|
159
|
+
return `[${args.ruleId}] ${loc}\n ${args.message}${hintLine}`;
|
|
160
|
+
}
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// ARRAY EXPRESSION WALK
|
|
163
|
+
// =============================================================================
|
|
164
|
+
/**
|
|
165
|
+
* Resolve an expression that's expected to be an array of primitive refs.
|
|
166
|
+
*
|
|
167
|
+
* Dispatches on node kind: ArrayLiteral → iterate elements; Identifier →
|
|
168
|
+
* follow the reference chain; anything else → deopt with a pinpointing reason.
|
|
169
|
+
*
|
|
170
|
+
* Mutates `out` with resolved refs and `state.deopt` on failure.
|
|
171
|
+
*/
|
|
172
|
+
function resolveArrayExpression(node, out, state) {
|
|
173
|
+
// Direct array literal: iterate elements.
|
|
174
|
+
if (Node.isArrayLiteralExpression(node)) {
|
|
175
|
+
for (const element of node.getElements()) {
|
|
176
|
+
resolveArrayElement(element, out, state);
|
|
177
|
+
if (state.deopt)
|
|
178
|
+
return; // stop at first deopt — we have a pinpoint
|
|
179
|
+
}
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Identifier: follow to declaration, then recurse into its initializer.
|
|
183
|
+
if (Node.isIdentifier(node)) {
|
|
184
|
+
const decl = followIdentifier(node, state);
|
|
185
|
+
if (!decl) {
|
|
186
|
+
// Fall back to the default-import-of-array shape:
|
|
187
|
+
// // target.ts
|
|
188
|
+
// export default [new LuaMCPServer(...), ...];
|
|
189
|
+
// // consumer.ts
|
|
190
|
+
// import servers from './target';
|
|
191
|
+
// mcpServers: servers;
|
|
192
|
+
// There's no `const servers = ...` to hand back as a declaration;
|
|
193
|
+
// instead, locate the default export expression and recurse into it.
|
|
194
|
+
const defaultExpr = followDefaultImportToExpression(node, state);
|
|
195
|
+
if (defaultExpr) {
|
|
196
|
+
resolveArrayExpression(defaultExpr, out, state);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
state.deopt = state.deopt ?? {
|
|
200
|
+
node,
|
|
201
|
+
reason: `cannot resolve identifier "${node.getText()}" to a declaration in the project`,
|
|
202
|
+
};
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Class/function declarations aren't arrays — a user writing
|
|
206
|
+
// `mcpServers: MyClass` is malformed; pinpoint it.
|
|
207
|
+
if (!Node.isVariableDeclaration(decl)) {
|
|
208
|
+
state.deopt = state.deopt ?? {
|
|
209
|
+
node,
|
|
210
|
+
reason: `identifier "${node.getText()}" resolves to a ${decl.getKindName()}, not an array`,
|
|
211
|
+
};
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const init = decl.getInitializer();
|
|
215
|
+
if (!init) {
|
|
216
|
+
state.deopt = state.deopt ?? {
|
|
217
|
+
node,
|
|
218
|
+
reason: `identifier "${node.getText()}" resolves to a declaration with no initializer`,
|
|
219
|
+
};
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
resolveArrayExpression(init, out, state);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// Parenthesized / type-asserted / as-const: unwrap and recurse.
|
|
226
|
+
if (Node.isParenthesizedExpression(node)) {
|
|
227
|
+
resolveArrayExpression(node.getExpression(), out, state);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (Node.isAsExpression(node) || Node.isTypeAssertion(node) || Node.isSatisfiesExpression(node)) {
|
|
231
|
+
resolveArrayExpression(node.getExpression(), out, state);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Property access: `all.mcp` where `all` is an object literal.
|
|
235
|
+
if (Node.isPropertyAccessExpression(node)) {
|
|
236
|
+
const value = resolvePropertyAccess(node, state);
|
|
237
|
+
if (!value) {
|
|
238
|
+
state.deopt = state.deopt ?? {
|
|
239
|
+
node,
|
|
240
|
+
reason: `property access "${node.getText()}" does not resolve to a static array`,
|
|
241
|
+
};
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
resolveArrayExpression(value, out, state);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// Array concat expressions like `a.concat(b)` or `[...a, ...b]` route
|
|
248
|
+
// through ArrayLiteral (the spread case above) or deopt here.
|
|
249
|
+
if (Node.isCallExpression(node)) {
|
|
250
|
+
state.deopt = state.deopt ?? {
|
|
251
|
+
node,
|
|
252
|
+
reason: `call expression "${truncate(node.getText(), 40)}" cannot be statically resolved to an array`,
|
|
253
|
+
};
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
state.deopt = state.deopt ?? {
|
|
257
|
+
node,
|
|
258
|
+
reason: `unsupported expression kind ${node.getKindName()}`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Resolve a single element within an ArrayLiteralExpression.
|
|
263
|
+
*
|
|
264
|
+
* Handles:
|
|
265
|
+
* - `new X({...})` → ResolvedRef pointing at the inline NewExpression
|
|
266
|
+
* - `foo` (Identifier) → follow to declaration, decide if it's a ref or an array
|
|
267
|
+
* - `...foo` (Spread) → recurse into the spread expression as an array
|
|
268
|
+
* - Anything else → deopt
|
|
269
|
+
*/
|
|
270
|
+
function resolveArrayElement(element, out, state) {
|
|
271
|
+
// Unwrap parens / assertions first.
|
|
272
|
+
let node = element;
|
|
273
|
+
while (Node.isParenthesizedExpression(node) ||
|
|
274
|
+
Node.isAsExpression(node) ||
|
|
275
|
+
Node.isTypeAssertion(node) ||
|
|
276
|
+
Node.isSatisfiesExpression(node)) {
|
|
277
|
+
node = node.getExpression();
|
|
278
|
+
}
|
|
279
|
+
// Spread: `...foo` — resolve as an array expression.
|
|
280
|
+
if (Node.isSpreadElement(node)) {
|
|
281
|
+
resolveArrayExpression(node.getExpression(), out, state);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// Inline `new X({...})` — record with identifier = class name.
|
|
285
|
+
//
|
|
286
|
+
// Two legitimate interpretations depending on the caller:
|
|
287
|
+
// 1. Tool-class pattern (SkillPlugin's `tools: [new GetUserDataTool()]`):
|
|
288
|
+
// the class name is imported from another project file and we
|
|
289
|
+
// need `sourceFile` to point at that file so `ToolPlugin.detect()`
|
|
290
|
+
// can find `export class GetUserDataTool` there.
|
|
291
|
+
// 2. Primitive-registration pattern (AgentTraverser's
|
|
292
|
+
// `mcpServers: [new LuaMCPServer({...})]`): the class name is
|
|
293
|
+
// external/generic (LuaMCPServer lives in the `lua-cli` package)
|
|
294
|
+
// and the actual primitive is the inline NewExpression itself,
|
|
295
|
+
// so `sourceFile` stays the consumer's file and AgentTraverser
|
|
296
|
+
// emits a deopt diagnostic (see `newExpression && !declaration`).
|
|
297
|
+
//
|
|
298
|
+
// Distinguishing them: try to follow the class identifier to a local
|
|
299
|
+
// ClassDeclaration. If we find one in a real project source file, we
|
|
300
|
+
// record it — that tells downstream plugins to look in the target
|
|
301
|
+
// file. If the declaration lives in a `.d.ts` / `node_modules` file
|
|
302
|
+
// (e.g. `LuaMCPServer` resolving to `lua-cli`'s ambient class via
|
|
303
|
+
// TS's aliased-symbol lookup), we treat it as external and keep the
|
|
304
|
+
// old permissive shape so AgentTraverser's `newExpression &&
|
|
305
|
+
// !declaration` deopt path still fires. Without this guard, inline
|
|
306
|
+
// primitive-registration cases (`mcpServers: [new LuaMCPServer(...)]`)
|
|
307
|
+
// would silently bind to the ambient class declaration and then
|
|
308
|
+
// silently fail to detect any primitive downstream.
|
|
309
|
+
if (Node.isNewExpression(node)) {
|
|
310
|
+
const classExpr = node.getExpression();
|
|
311
|
+
const className = classExpr.getText();
|
|
312
|
+
if (Node.isIdentifier(classExpr)) {
|
|
313
|
+
const decl = followIdentifier(classExpr, state);
|
|
314
|
+
if (decl && Node.isClassDeclaration(decl)) {
|
|
315
|
+
const declFile = decl.getSourceFile();
|
|
316
|
+
const isProjectFile = !declFile.isDeclarationFile() && !declFile.isInNodeModules();
|
|
317
|
+
if (isProjectFile) {
|
|
318
|
+
out.push({
|
|
319
|
+
identifierName: className,
|
|
320
|
+
sourceFile: declFile,
|
|
321
|
+
declaration: decl,
|
|
322
|
+
newExpression: node,
|
|
323
|
+
viaDefaultImport: isDefaultImportBinding(classExpr),
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
out.push({
|
|
330
|
+
identifierName: className,
|
|
331
|
+
sourceFile: node.getSourceFile(),
|
|
332
|
+
newExpression: node,
|
|
333
|
+
});
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
// `foo` — follow to declaration.
|
|
337
|
+
if (Node.isIdentifier(node)) {
|
|
338
|
+
// Record whether this identifier is a consumer-side default-import
|
|
339
|
+
// binding (`import whatever from './target'`). Propagated onto every
|
|
340
|
+
// ref produced below so downstream plugin.detect() matching can
|
|
341
|
+
// prefer the target's default export — the consumer's local name is
|
|
342
|
+
// arbitrary and won't line up with any exportName on the target side.
|
|
343
|
+
const viaDefaultImport = isDefaultImportBinding(node);
|
|
344
|
+
const decl = followIdentifier(node, state);
|
|
345
|
+
if (!decl) {
|
|
346
|
+
// Fallback: the identifier IS an import, but the target uses an
|
|
347
|
+
// inline `export default defineX({...})` / `export default new X({...})`
|
|
348
|
+
// pattern that doesn't bind to a named declaration. We can still
|
|
349
|
+
// report the reference because the plugin's `detect()` will walk the
|
|
350
|
+
// target file and find the inline primitive by itself.
|
|
351
|
+
const fileLevelRef = findFileLevelImport(node, state);
|
|
352
|
+
if (fileLevelRef) {
|
|
353
|
+
out.push(fileLevelRef);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
state.deopt = state.deopt ?? {
|
|
357
|
+
node,
|
|
358
|
+
reason: `cannot resolve identifier "${node.getText()}" to a declaration in the project`,
|
|
359
|
+
};
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// If the declaration IS a class/function (like `new` used as a var), treat as ref.
|
|
363
|
+
if (Node.isClassDeclaration(decl) || Node.isFunctionDeclaration(decl)) {
|
|
364
|
+
out.push({
|
|
365
|
+
identifierName: node.getText(),
|
|
366
|
+
sourceFile: decl.getSourceFile(),
|
|
367
|
+
declaration: decl,
|
|
368
|
+
viaDefaultImport,
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const init = decl.getInitializer();
|
|
373
|
+
if (!init) {
|
|
374
|
+
// Exported as type-only / ambient → not a primitive we can compile.
|
|
375
|
+
state.deopt = state.deopt ?? {
|
|
376
|
+
node,
|
|
377
|
+
reason: `identifier "${node.getText()}" has no initializer`,
|
|
378
|
+
};
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
// Variable points at another array — this is a pass-through, recurse.
|
|
382
|
+
if (Node.isArrayLiteralExpression(init)) {
|
|
383
|
+
resolveArrayExpression(init, out, state);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
// Variable points at another identifier — keep following.
|
|
387
|
+
if (Node.isIdentifier(init)) {
|
|
388
|
+
resolveArrayElement(init, out, state);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
// Variable points at a `new X({...})` — leaf ref.
|
|
392
|
+
if (Node.isNewExpression(init)) {
|
|
393
|
+
out.push({
|
|
394
|
+
identifierName: node.getText(),
|
|
395
|
+
sourceFile: decl.getSourceFile(),
|
|
396
|
+
declaration: decl,
|
|
397
|
+
newExpression: init,
|
|
398
|
+
viaDefaultImport,
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// Variable points at a `defineX({...})` call — also a leaf ref.
|
|
403
|
+
if (Node.isCallExpression(init)) {
|
|
404
|
+
out.push({
|
|
405
|
+
identifierName: node.getText(),
|
|
406
|
+
sourceFile: decl.getSourceFile(),
|
|
407
|
+
declaration: decl,
|
|
408
|
+
viaDefaultImport,
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
// Any other initializer (property access, complex expression) — treat
|
|
413
|
+
// as an opaque leaf ref. Plugins downstream will attempt detection.
|
|
414
|
+
// We take the name as authoritative; if the plugin can't match it we
|
|
415
|
+
// fall back to a warning, not a silent drop.
|
|
416
|
+
out.push({
|
|
417
|
+
identifierName: node.getText(),
|
|
418
|
+
sourceFile: decl.getSourceFile(),
|
|
419
|
+
declaration: decl,
|
|
420
|
+
viaDefaultImport,
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
// Property access as an element (e.g. `registry.auth`) — resolve.
|
|
425
|
+
if (Node.isPropertyAccessExpression(node)) {
|
|
426
|
+
const value = resolvePropertyAccess(node, state);
|
|
427
|
+
if (!value) {
|
|
428
|
+
state.deopt = state.deopt ?? {
|
|
429
|
+
node,
|
|
430
|
+
reason: `property access "${node.getText()}" does not resolve to a primitive`,
|
|
431
|
+
};
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
resolveArrayElement(value, out, state);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
state.deopt = state.deopt ?? {
|
|
438
|
+
node,
|
|
439
|
+
reason: `unsupported array element kind ${node.getKindName()}`,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
// =============================================================================
|
|
443
|
+
// IDENTIFIER → DECLARATION RESOLUTION
|
|
444
|
+
// =============================================================================
|
|
445
|
+
/**
|
|
446
|
+
* Follow an Identifier to the VariableDeclaration/ClassDeclaration/
|
|
447
|
+
* FunctionDeclaration it ultimately refers to.
|
|
448
|
+
*
|
|
449
|
+
* Order of attempts (cheapest first; matches ts-morph issue #644 guidance):
|
|
450
|
+
*
|
|
451
|
+
* 1. Same-file variable declaration (O(1) symbol-table lookup).
|
|
452
|
+
* 2. Same-file class/function declaration.
|
|
453
|
+
* 3. Follow an `import` to the target SourceFile, then:
|
|
454
|
+
* - Named export in that file (or a variable with the same name)
|
|
455
|
+
* - Re-export chain (`export { x } from './y'`, `export * from './y'`)
|
|
456
|
+
* - Default export matching the imported default binding
|
|
457
|
+
* 4. Symbol-based fallback (`getAliasedSymbol`) — only if the manual
|
|
458
|
+
* walk exhausted. This is the slow path (triggers TS binding) but
|
|
459
|
+
* covers exotic cases the manual walk doesn't model.
|
|
460
|
+
*
|
|
461
|
+
* Cycle detection: we mark `<filePath>#<identifier>` in `state.visited`
|
|
462
|
+
* before recursing across files. A revisit short-circuits to undefined
|
|
463
|
+
* rather than looping — the caller emits a deopt with a clear reason.
|
|
464
|
+
*/
|
|
465
|
+
function followIdentifier(node, state) {
|
|
466
|
+
const name = node.getText();
|
|
467
|
+
const sourceFile = node.getSourceFile();
|
|
468
|
+
const visitKey = `${sourceFile.getFilePath()}#${name}`;
|
|
469
|
+
if (state.visited.has(visitKey)) {
|
|
470
|
+
state.log(` cycle detected following ${visitKey}; stopping walk`);
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
state.visited.add(visitKey);
|
|
474
|
+
// Step 1: same-file variable.
|
|
475
|
+
const localVar = sourceFile.getVariableDeclaration(name);
|
|
476
|
+
if (localVar)
|
|
477
|
+
return localVar;
|
|
478
|
+
// Step 2: same-file class/function.
|
|
479
|
+
const localClass = sourceFile.getClass(name);
|
|
480
|
+
if (localClass)
|
|
481
|
+
return localClass;
|
|
482
|
+
const localFn = sourceFile.getFunction(name);
|
|
483
|
+
if (localFn)
|
|
484
|
+
return localFn;
|
|
485
|
+
// Step 3: imports.
|
|
486
|
+
const viaImport = followImport(name, sourceFile, state);
|
|
487
|
+
if (viaImport)
|
|
488
|
+
return viaImport;
|
|
489
|
+
// Step 4: symbol fallback — only reached when the manual walk exhausts.
|
|
490
|
+
// Intentionally last because it triggers full TS binding.
|
|
491
|
+
return followSymbol(node, state);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* When `node` is a default-imported identifier (`import servers from './x'`),
|
|
495
|
+
* return the expression on the right-hand side of that file's
|
|
496
|
+
* `export default ...`, so the array walk can recurse into it.
|
|
497
|
+
*
|
|
498
|
+
* Handles the case the declaration-based path can't:
|
|
499
|
+
* // target.ts
|
|
500
|
+
* export default [a, b];
|
|
501
|
+
* There's no `VariableDeclaration` named `servers` here — the export
|
|
502
|
+
* assignment's expression is the array itself, and we have to hand the
|
|
503
|
+
* whole expression back to the caller so it can iterate the elements.
|
|
504
|
+
*
|
|
505
|
+
* Returns undefined when the identifier isn't a default import, or when
|
|
506
|
+
* the target uses `export default someIdentifier` (already handled by
|
|
507
|
+
* `resolveDefaultInFile` → declaration path) or is otherwise opaque.
|
|
508
|
+
*/
|
|
509
|
+
function followDefaultImportToExpression(node, state) {
|
|
510
|
+
const name = node.getText();
|
|
511
|
+
const sourceFile = node.getSourceFile();
|
|
512
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
513
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
514
|
+
if (!defaultImport || defaultImport.getText() !== name)
|
|
515
|
+
continue;
|
|
516
|
+
const target = loadImportTarget(importDecl, state, sourceFile.getFilePath());
|
|
517
|
+
if (!target)
|
|
518
|
+
return undefined;
|
|
519
|
+
for (const assign of target.getExportAssignments()) {
|
|
520
|
+
if (assign.isExportEquals())
|
|
521
|
+
continue;
|
|
522
|
+
return assign.getExpression();
|
|
523
|
+
}
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Locate the target SourceFile for an imported identifier without requiring
|
|
530
|
+
* an intermediary declaration on that side. Used for the common pattern:
|
|
531
|
+
*
|
|
532
|
+
* // target.ts
|
|
533
|
+
* export default defineJob({ ... });
|
|
534
|
+
* // consumer.ts
|
|
535
|
+
* import syncJob from './target';
|
|
536
|
+
* jobs: [syncJob];
|
|
537
|
+
*
|
|
538
|
+
* There's no `const syncJob = ...` on the target side, so `followIdentifier`
|
|
539
|
+
* can't produce a declaration — but we still know which file holds the
|
|
540
|
+
* primitive and what name the consumer used. The caller's plugin will walk
|
|
541
|
+
* that file with its own `detect()` and find the inline primitive.
|
|
542
|
+
*/
|
|
543
|
+
function findFileLevelImport(node, state) {
|
|
544
|
+
const name = node.getText();
|
|
545
|
+
const sourceFile = node.getSourceFile();
|
|
546
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
547
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
548
|
+
const matchesDefault = defaultImport?.getText() === name;
|
|
549
|
+
const matchesNamed = importDecl
|
|
550
|
+
.getNamedImports()
|
|
551
|
+
.some((n) => (n.getAliasNode()?.getText() ?? n.getName()) === name);
|
|
552
|
+
if (!matchesDefault && !matchesNamed)
|
|
553
|
+
continue;
|
|
554
|
+
const target = loadImportTarget(importDecl, state, sourceFile.getFilePath());
|
|
555
|
+
if (!target)
|
|
556
|
+
return undefined;
|
|
557
|
+
return {
|
|
558
|
+
identifierName: name,
|
|
559
|
+
sourceFile: target,
|
|
560
|
+
viaDefaultImport: matchesDefault,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Find a same-named ImportDeclaration and resolve into the target file.
|
|
567
|
+
*
|
|
568
|
+
* Handles:
|
|
569
|
+
* - `import foo from './x'` (default)
|
|
570
|
+
* - `import { foo } from './x'` (named)
|
|
571
|
+
* - `import { foo as bar } from './x'` (named w/ alias)
|
|
572
|
+
*
|
|
573
|
+
* Namespace imports (`import * as ns from './x'`) aren't resolved here
|
|
574
|
+
* — a bare `ns` identifier can't stand in for a primitive array; the
|
|
575
|
+
* only meaningful use is `ns.foo`, which is resolved in
|
|
576
|
+
* `resolvePropertyAccess` via `followNamespaceImport`.
|
|
577
|
+
*
|
|
578
|
+
* After locating the import, loads the target file into the project
|
|
579
|
+
* on demand and recurses inside it via `resolveInFile()`.
|
|
580
|
+
*/
|
|
581
|
+
function followImport(name, sourceFile, state) {
|
|
582
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
583
|
+
// Default: `import foo from './x'`
|
|
584
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
585
|
+
if (defaultImport && defaultImport.getText() === name) {
|
|
586
|
+
const targetFile = loadImportTarget(importDecl, state, sourceFile.getFilePath());
|
|
587
|
+
if (!targetFile)
|
|
588
|
+
return undefined;
|
|
589
|
+
return resolveDefaultInFile(targetFile, state);
|
|
590
|
+
}
|
|
591
|
+
// Named: `import { foo } from './x'` or `import { foo as bar } from './x'`
|
|
592
|
+
for (const named of importDecl.getNamedImports()) {
|
|
593
|
+
const localBinding = named.getAliasNode()?.getText() ?? named.getName();
|
|
594
|
+
if (localBinding !== name)
|
|
595
|
+
continue;
|
|
596
|
+
const remoteName = named.getName(); // the name as exported by target
|
|
597
|
+
const targetFile = loadImportTarget(importDecl, state, sourceFile.getFilePath());
|
|
598
|
+
if (!targetFile)
|
|
599
|
+
return undefined;
|
|
600
|
+
return resolveInFile(remoteName, targetFile, state);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return undefined;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Load the SourceFile that an `import` or `export ... from` declaration
|
|
607
|
+
* points at, using the ts-morph built-in resolver where it works, and
|
|
608
|
+
* falling back to the project's tsconfig `paths` aliases + relative FS
|
|
609
|
+
* resolution otherwise.
|
|
610
|
+
*
|
|
611
|
+
* Works uniformly for `ImportDeclaration` and `ExportDeclaration` because
|
|
612
|
+
* both expose `getModuleSpecifierSourceFile()` / `getModuleSpecifierValue()`
|
|
613
|
+
* (ExportDeclaration returns undefined when there's no `from` clause).
|
|
614
|
+
*
|
|
615
|
+
* Files discovered this way are added to the project lazily so the rest
|
|
616
|
+
* of the resolver (and downstream plugins) can use them as first-class
|
|
617
|
+
* SourceFiles.
|
|
618
|
+
*/
|
|
619
|
+
function loadImportTarget(decl, state, fromFile) {
|
|
620
|
+
// Fast path: ts-morph already figured it out.
|
|
621
|
+
const viaTsMorph = decl.getModuleSpecifierSourceFile();
|
|
622
|
+
if (viaTsMorph)
|
|
623
|
+
return viaTsMorph;
|
|
624
|
+
// Slow path: shared resolveModuleSpecifier in utils/path-resolver.ts
|
|
625
|
+
// — the same helper AgentTraverser uses, so drift between the two
|
|
626
|
+
// sites (previously a real concern) is impossible.
|
|
627
|
+
const specifier = decl.getModuleSpecifierValue();
|
|
628
|
+
if (!specifier)
|
|
629
|
+
return undefined; // bare `export { x }` with no source
|
|
630
|
+
const resolved = resolveModuleSpecifier(specifier, fromFile, state.rootDir);
|
|
631
|
+
if (!resolved)
|
|
632
|
+
return undefined;
|
|
633
|
+
try {
|
|
634
|
+
const existing = state.project.getSourceFile(resolved);
|
|
635
|
+
if (existing)
|
|
636
|
+
return existing;
|
|
637
|
+
return state.project.addSourceFileAtPath(resolved);
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
state.log(` failed to load ${resolved}: ${String(err)}`);
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Resolve an `export { x } from './y'` / `export * from './y'` /
|
|
646
|
+
* same-file declaration named `name` in the given file.
|
|
647
|
+
*
|
|
648
|
+
* Walks the re-export graph breadth-first until it finds the underlying
|
|
649
|
+
* declaration. Guarded by `state.visited` against cyclic re-exports.
|
|
650
|
+
*/
|
|
651
|
+
function resolveInFile(name, sourceFile, state) {
|
|
652
|
+
const visitKey = `${sourceFile.getFilePath()}#${name}`;
|
|
653
|
+
if (state.visited.has(visitKey))
|
|
654
|
+
return undefined;
|
|
655
|
+
state.visited.add(visitKey);
|
|
656
|
+
// Same-file declaration wins.
|
|
657
|
+
const local = sourceFile.getVariableDeclaration(name)
|
|
658
|
+
?? sourceFile.getClass(name)
|
|
659
|
+
?? sourceFile.getFunction(name);
|
|
660
|
+
if (local)
|
|
661
|
+
return local;
|
|
662
|
+
// Re-exports with a module specifier: `export { x } from './y'`,
|
|
663
|
+
// `export { x as y } from './z'`, `export * from './y'`, `export * as ns from './y'`.
|
|
664
|
+
for (const exp of sourceFile.getExportDeclarations()) {
|
|
665
|
+
if (!exp.getModuleSpecifierValue())
|
|
666
|
+
continue;
|
|
667
|
+
const target = loadImportTarget(exp, state, sourceFile.getFilePath());
|
|
668
|
+
if (!target)
|
|
669
|
+
continue;
|
|
670
|
+
const matched = findInNamedExports(exp, name);
|
|
671
|
+
if (matched) {
|
|
672
|
+
return resolveInFile(matched, target, state);
|
|
673
|
+
}
|
|
674
|
+
// `export * from './y'` — ts-morph models this as an ExportDeclaration
|
|
675
|
+
// with a module specifier, no named exports, and no namespace export.
|
|
676
|
+
// Recurse to search the target exhaustively.
|
|
677
|
+
const isWildcard = exp.getNamedExports().length === 0 && !exp.getNamespaceExport();
|
|
678
|
+
if (isWildcard) {
|
|
679
|
+
const viaWildcard = resolveInFile(name, target, state);
|
|
680
|
+
if (viaWildcard)
|
|
681
|
+
return viaWildcard;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
// Re-exports without a module specifier: `export { x }` (rebind within
|
|
685
|
+
// the same file, possibly with a rename).
|
|
686
|
+
for (const exp of sourceFile.getExportDeclarations()) {
|
|
687
|
+
if (exp.getModuleSpecifierValue())
|
|
688
|
+
continue;
|
|
689
|
+
const matched = findInNamedExports(exp, name);
|
|
690
|
+
if (matched) {
|
|
691
|
+
return resolveInFile(matched, sourceFile, state);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// Chase symbol as a last resort — expensive, but handles exotic barrels.
|
|
695
|
+
const symbolResult = followSymbolInFile(name, sourceFile, state);
|
|
696
|
+
if (symbolResult)
|
|
697
|
+
return symbolResult;
|
|
698
|
+
return undefined;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Find the `default` export in a file. Typical shapes:
|
|
702
|
+
* export default foo → VariableDeclaration for `foo`
|
|
703
|
+
* export default new X({...}) → synthesized VariableDeclaration? No — return undefined
|
|
704
|
+
* and let the caller handle it (we don't manufacture decls).
|
|
705
|
+
* export default function X() → FunctionDeclaration
|
|
706
|
+
* export default class X {} → ClassDeclaration
|
|
707
|
+
*
|
|
708
|
+
* Returns undefined for inline `export default <expr>` patterns that don't
|
|
709
|
+
* bind to a named declaration — those cases bubble up as a deopt with a
|
|
710
|
+
* clear message upstream.
|
|
711
|
+
*/
|
|
712
|
+
function resolveDefaultInFile(sourceFile, state) {
|
|
713
|
+
for (const assign of sourceFile.getExportAssignments()) {
|
|
714
|
+
if (assign.isExportEquals())
|
|
715
|
+
continue;
|
|
716
|
+
const expr = assign.getExpression();
|
|
717
|
+
if (Node.isIdentifier(expr)) {
|
|
718
|
+
return resolveInFile(expr.getText(), sourceFile, state);
|
|
719
|
+
}
|
|
720
|
+
// `export default new X(...)` / `export default defineX(...)` —
|
|
721
|
+
// the caller has to treat the whole file as the unit.
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
// `export default function X()` / `export default class X {}`
|
|
725
|
+
for (const cls of sourceFile.getClasses()) {
|
|
726
|
+
if (cls.isDefaultExport())
|
|
727
|
+
return cls;
|
|
728
|
+
}
|
|
729
|
+
for (const fn of sourceFile.getFunctions()) {
|
|
730
|
+
if (fn.isDefaultExport())
|
|
731
|
+
return fn;
|
|
732
|
+
}
|
|
733
|
+
// Re-export-as-default:
|
|
734
|
+
// export { foo as default } from './other'
|
|
735
|
+
// export { foo as default } (same-file, equivalent to
|
|
736
|
+
// `export default foo`)
|
|
737
|
+
// These don't show up as ExportAssignments or `isDefaultExport()`
|
|
738
|
+
// declarations — they're ExportDeclarations with a spec aliased to
|
|
739
|
+
// `default`. Without this branch the walk falls back to
|
|
740
|
+
// `findFileLevelImport`, which lands on the barrel file instead of
|
|
741
|
+
// the ultimate source, and plugin.detect() emits a spurious
|
|
742
|
+
// `lua/missing-primitive-declaration` on a file that legitimately
|
|
743
|
+
// has no primitive in it.
|
|
744
|
+
for (const exp of sourceFile.getExportDeclarations()) {
|
|
745
|
+
for (const spec of exp.getNamedExports()) {
|
|
746
|
+
const alias = spec.getAliasNode()?.getText();
|
|
747
|
+
if (alias !== 'default')
|
|
748
|
+
continue;
|
|
749
|
+
const remoteName = spec.getName();
|
|
750
|
+
const moduleSpecifier = exp.getModuleSpecifierValue();
|
|
751
|
+
if (moduleSpecifier) {
|
|
752
|
+
// Cross-file: `export { foo as default } from './other'`
|
|
753
|
+
const target = loadImportTarget(exp, state, sourceFile.getFilePath());
|
|
754
|
+
if (!target)
|
|
755
|
+
return undefined;
|
|
756
|
+
return resolveInFile(remoteName, target, state);
|
|
757
|
+
}
|
|
758
|
+
// Same-file: `export { foo as default }` without a `from` clause
|
|
759
|
+
return resolveInFile(remoteName, sourceFile, state);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return undefined;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Return the name (as written in the source module) that a named export
|
|
766
|
+
* specifier maps to, or undefined if this declaration doesn't export `name`.
|
|
767
|
+
*
|
|
768
|
+
* export { foo } → findInNamedExports(_, 'foo') = 'foo'
|
|
769
|
+
* export { foo as bar } → findInNamedExports(_, 'bar') = 'foo'
|
|
770
|
+
*
|
|
771
|
+
* The returned name is used to recurse into the source module because
|
|
772
|
+
* that's what THAT module exports, not the alias used here.
|
|
773
|
+
*/
|
|
774
|
+
function findInNamedExports(exp, name) {
|
|
775
|
+
for (const spec of exp.getNamedExports()) {
|
|
776
|
+
const localName = spec.getAliasNode()?.getText() ?? spec.getName();
|
|
777
|
+
if (localName === name)
|
|
778
|
+
return spec.getName();
|
|
779
|
+
}
|
|
780
|
+
return undefined;
|
|
781
|
+
}
|
|
782
|
+
// =============================================================================
|
|
783
|
+
// SYMBOL FALLBACK (slow path)
|
|
784
|
+
// =============================================================================
|
|
785
|
+
/**
|
|
786
|
+
* Last-resort identifier resolution via the TypeChecker's symbol graph.
|
|
787
|
+
* Handles re-export shapes the manual walk above doesn't model (e.g. some
|
|
788
|
+
* exotic `export type` + `export` combinations, declaration-merging barrels).
|
|
789
|
+
*
|
|
790
|
+
* Expensive — triggers full symbol binding — so we only reach it when the
|
|
791
|
+
* cheaper paths above have returned undefined. See ts-morph issue #644.
|
|
792
|
+
*/
|
|
793
|
+
function followSymbol(node, state) {
|
|
794
|
+
try {
|
|
795
|
+
const symbol = node.getSymbol();
|
|
796
|
+
if (!symbol)
|
|
797
|
+
return undefined;
|
|
798
|
+
const aliased = symbol.isAlias()
|
|
799
|
+
? node.getSourceFile().getProject().getTypeChecker().getAliasedSymbol(symbol) ?? symbol
|
|
800
|
+
: symbol;
|
|
801
|
+
for (const decl of aliased.getDeclarations()) {
|
|
802
|
+
if (Node.isVariableDeclaration(decl) || Node.isClassDeclaration(decl) || Node.isFunctionDeclaration(decl)) {
|
|
803
|
+
return decl;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
catch (err) {
|
|
808
|
+
state.log(` symbol fallback failed: ${String(err)}`);
|
|
809
|
+
}
|
|
810
|
+
return undefined;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Symbol fallback keyed by file + name rather than an existing Identifier
|
|
814
|
+
* node. Used when chasing re-exports that point at a name that's declared
|
|
815
|
+
* further down the graph than our manual walk can follow.
|
|
816
|
+
*/
|
|
817
|
+
function followSymbolInFile(name, sourceFile, state) {
|
|
818
|
+
// Count every invocation of this slow path, not just ones that
|
|
819
|
+
// succeed — the point of the telemetry is to know how often we hit
|
|
820
|
+
// `getExportedDeclarations`, which triggers full TS binding and is
|
|
821
|
+
// ~1000x slower than the AST-based walks above. Surfaced by
|
|
822
|
+
// `resolveArrayRefs` under SMOKE_DEBUG.
|
|
823
|
+
state.slowPathHits++;
|
|
824
|
+
try {
|
|
825
|
+
const exportedMap = sourceFile.getExportedDeclarations();
|
|
826
|
+
const decls = exportedMap.get(name);
|
|
827
|
+
if (!decls)
|
|
828
|
+
return undefined;
|
|
829
|
+
for (const decl of decls) {
|
|
830
|
+
if (Node.isVariableDeclaration(decl) || Node.isClassDeclaration(decl) || Node.isFunctionDeclaration(decl)) {
|
|
831
|
+
return decl;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
catch (err) {
|
|
836
|
+
state.log(` symbol-in-file fallback failed for ${name}: ${String(err)}`);
|
|
837
|
+
}
|
|
838
|
+
return undefined;
|
|
839
|
+
}
|
|
840
|
+
// =============================================================================
|
|
841
|
+
// PROPERTY ACCESS RESOLUTION
|
|
842
|
+
// =============================================================================
|
|
843
|
+
/**
|
|
844
|
+
* Resolve `a.b` where `a` is a same-file object literal, an imported object
|
|
845
|
+
* literal, or a namespace import (`import * as ns from './x'`), and `b` is
|
|
846
|
+
* a known property / named export. Returns the value node (typically an
|
|
847
|
+
* ArrayLiteralExpression) so the caller can recurse into it.
|
|
848
|
+
*
|
|
849
|
+
* Deep chains like `a.b.c` are handled recursively.
|
|
850
|
+
*
|
|
851
|
+
* Namespace imports are handled here — `followImport` can't express them
|
|
852
|
+
* because `ns` has no backing VariableDeclaration. From the resolver's
|
|
853
|
+
* point of view, `ns.foo` is equivalent to `import { foo } from './x'`,
|
|
854
|
+
* so we short-circuit to looking up `foo` in the target file's scope and
|
|
855
|
+
* returning its initializer. The returned node lives in the target file,
|
|
856
|
+
* so any identifiers inside it resolve in the right scope on recursion.
|
|
857
|
+
*/
|
|
858
|
+
function resolvePropertyAccess(node, state) {
|
|
859
|
+
if (!Node.isPropertyAccessExpression(node))
|
|
860
|
+
return undefined;
|
|
861
|
+
const propertyName = node.getName();
|
|
862
|
+
const objectExpr = node.getExpression();
|
|
863
|
+
// Namespace-import short-circuit: `ns.foo` where `ns` came from
|
|
864
|
+
// `import * as ns from './x'`. Look `foo` up as a named export on the
|
|
865
|
+
// target file and return its initializer directly.
|
|
866
|
+
if (Node.isIdentifier(objectExpr)) {
|
|
867
|
+
const namespaceTarget = followNamespaceImport(objectExpr, state);
|
|
868
|
+
if (namespaceTarget) {
|
|
869
|
+
const decl = resolveInFile(propertyName, namespaceTarget, state);
|
|
870
|
+
if (decl && Node.isVariableDeclaration(decl)) {
|
|
871
|
+
return decl.getInitializer();
|
|
872
|
+
}
|
|
873
|
+
// Class/function export under a namespace isn't a valid object-literal
|
|
874
|
+
// base — fall through to deopt at the call site.
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// Resolve the object part.
|
|
879
|
+
let objectNode = objectExpr;
|
|
880
|
+
if (Node.isIdentifier(objectExpr)) {
|
|
881
|
+
const decl = followIdentifier(objectExpr, state);
|
|
882
|
+
// Only VariableDeclarations have initializers; class/function
|
|
883
|
+
// declarations can't back an object-literal property chain.
|
|
884
|
+
objectNode = decl && Node.isVariableDeclaration(decl) ? decl.getInitializer() : undefined;
|
|
885
|
+
}
|
|
886
|
+
else if (Node.isPropertyAccessExpression(objectExpr)) {
|
|
887
|
+
objectNode = resolvePropertyAccess(objectExpr, state);
|
|
888
|
+
}
|
|
889
|
+
if (!objectNode || !Node.isObjectLiteralExpression(objectNode)) {
|
|
890
|
+
return undefined;
|
|
891
|
+
}
|
|
892
|
+
const prop = objectNode.getProperty(propertyName);
|
|
893
|
+
if (!prop || !Node.isPropertyAssignment(prop))
|
|
894
|
+
return undefined;
|
|
895
|
+
return prop.getInitializer();
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* If `identifier` is the local binding of an `import * as ns from './x'`
|
|
899
|
+
* declaration, return the target SourceFile. Otherwise undefined.
|
|
900
|
+
*
|
|
901
|
+
* Narrowly scoped helper for `resolvePropertyAccess` — namespace imports
|
|
902
|
+
* outside a property-access context would just deopt (you can't register
|
|
903
|
+
* a primitive array as `mcpServers: ns` since `ns` is a module object,
|
|
904
|
+
* not an array), so this is the only place it needs to exist.
|
|
905
|
+
*/
|
|
906
|
+
function followNamespaceImport(identifier, state) {
|
|
907
|
+
const name = identifier.getText();
|
|
908
|
+
const sourceFile = identifier.getSourceFile();
|
|
909
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
910
|
+
const ns = importDecl.getNamespaceImport();
|
|
911
|
+
if (!ns || ns.getText() !== name)
|
|
912
|
+
continue;
|
|
913
|
+
return loadImportTarget(importDecl, state, sourceFile.getFilePath());
|
|
914
|
+
}
|
|
915
|
+
return undefined;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Return true when `identifier` is the local binding of a default import
|
|
919
|
+
* (`import whatever from './target'`). Used to mark resulting `ResolvedRef`s
|
|
920
|
+
* so downstream plugin.detect() matching can prefer the target's default
|
|
921
|
+
* export — the consumer-side binding name is arbitrary and uncorrelated
|
|
922
|
+
* with any exportName on the target side.
|
|
923
|
+
*/
|
|
924
|
+
function isDefaultImportBinding(identifier) {
|
|
925
|
+
const name = identifier.getText();
|
|
926
|
+
const sourceFile = identifier.getSourceFile();
|
|
927
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
928
|
+
const defaultImport = importDecl.getDefaultImport();
|
|
929
|
+
if (defaultImport && defaultImport.getText() === name)
|
|
930
|
+
return true;
|
|
931
|
+
}
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
// =============================================================================
|
|
935
|
+
// ESCAPE HATCH
|
|
936
|
+
// =============================================================================
|
|
937
|
+
/**
|
|
938
|
+
* Check for a `/* @lua-ignore-ref *\/` pragma on the node or its enclosing
|
|
939
|
+
* property assignment / array element. Matches the spirit of `@ts-ignore`
|
|
940
|
+
* and `eslint-disable-next-line` — a small, stable string users can write
|
|
941
|
+
* in source to opt out of this particular static check without silencing
|
|
942
|
+
* everything else.
|
|
943
|
+
*
|
|
944
|
+
* Accepts all of these placements:
|
|
945
|
+
*
|
|
946
|
+
* // @lua-ignore-ref (leading line comment)
|
|
947
|
+
* mcpServers: buildDynamically()
|
|
948
|
+
*
|
|
949
|
+
* mcpServers: /* @lua-ignore-ref *\/ buildDynamically() (inline block)
|
|
950
|
+
*
|
|
951
|
+
* mcpServers: buildDynamically() // @lua-ignore-ref (trailing line)
|
|
952
|
+
*
|
|
953
|
+
* mcpServers: buildDynamically() /* @lua-ignore-ref *\/ (trailing block)
|
|
954
|
+
*
|
|
955
|
+
* /** @lua-ignore-ref *\/ (JSDoc-style leading)
|
|
956
|
+
* mcpServers: buildDynamically()
|
|
957
|
+
*
|
|
958
|
+
* We can't rely on `getLeadingCommentRanges()` alone because ts-morph
|
|
959
|
+
* doesn't surface inline block comments between a `:` and the expression
|
|
960
|
+
* (they're parsed as trivia the scanner drops). Instead we inspect the
|
|
961
|
+
* raw source slice on both sides of each candidate node — leading trivia
|
|
962
|
+
* via `FullStart..Start`, trailing trivia via `End..(next sibling's
|
|
963
|
+
* FullStart or parent End)` — which covers every placement a user who
|
|
964
|
+
* read the "ESLint-style" description would naturally reach for.
|
|
965
|
+
*/
|
|
966
|
+
// Match the pragma only when it appears inside a real comment delimiter, so
|
|
967
|
+
// a plain mention in surrounding prose — e.g. `// TODO: consider @lua-ignore-ref
|
|
968
|
+
// here instead` in some far-away line that happens to end up in the same
|
|
969
|
+
// trivia slice — doesn't accidentally disable the check. The `\b` after the
|
|
970
|
+
// pragma name also prevents matching something like `@lua-ignore-refactor`.
|
|
971
|
+
// The `\*?` after `/*` lets JSDoc-style `/** @lua-ignore-ref */` also match
|
|
972
|
+
// — that's many users' reflex for "tool-recognized comment" and costs us
|
|
973
|
+
// nothing to support.
|
|
974
|
+
const IGNORE_REF_PRAGMA_REGEX = /(?:\/\*\*?|\/\/)\s*@lua-ignore-ref\b/;
|
|
975
|
+
function hasIgnoreRefPragma(node) {
|
|
976
|
+
// Leading trivia slice: [FullStart, Start). Captures comments on the
|
|
977
|
+
// line(s) before a node or in the trivia between `:` and the node.
|
|
978
|
+
const leadingSliceHasPragma = (candidate) => {
|
|
979
|
+
if (!candidate)
|
|
980
|
+
return false;
|
|
981
|
+
const sourceText = candidate.getSourceFile().getFullText();
|
|
982
|
+
const slice = sourceText.slice(candidate.getFullStart(), candidate.getStart());
|
|
983
|
+
return IGNORE_REF_PRAGMA_REGEX.test(slice);
|
|
984
|
+
};
|
|
985
|
+
// Trailing trivia slice: [End, nextBoundary). `nextBoundary` is the
|
|
986
|
+
// next sibling's FullStart if any (its FullStart includes all trivia
|
|
987
|
+
// up to itself, so the slice between us and it is our trailing
|
|
988
|
+
// trivia), otherwise the parent's End. This catches `// @lua-ignore-ref`
|
|
989
|
+
// written after the property value — a common user reflex from ESLint.
|
|
990
|
+
const trailingSliceHasPragma = (candidate) => {
|
|
991
|
+
if (!candidate)
|
|
992
|
+
return false;
|
|
993
|
+
const sourceText = candidate.getSourceFile().getFullText();
|
|
994
|
+
const end = candidate.getEnd();
|
|
995
|
+
let nextBoundary;
|
|
996
|
+
const parent = candidate.getParent();
|
|
997
|
+
if (parent) {
|
|
998
|
+
const siblings = parent.getChildren();
|
|
999
|
+
const idx = siblings.indexOf(candidate);
|
|
1000
|
+
if (idx >= 0 && idx + 1 < siblings.length) {
|
|
1001
|
+
nextBoundary = siblings[idx + 1].getFullStart();
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
nextBoundary = parent.getEnd();
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (nextBoundary === undefined || nextBoundary <= end)
|
|
1008
|
+
return false;
|
|
1009
|
+
const slice = sourceText.slice(end, nextBoundary);
|
|
1010
|
+
return IGNORE_REF_PRAGMA_REGEX.test(slice);
|
|
1011
|
+
};
|
|
1012
|
+
if (leadingSliceHasPragma(node))
|
|
1013
|
+
return true;
|
|
1014
|
+
if (leadingSliceHasPragma(node.getParent()))
|
|
1015
|
+
return true;
|
|
1016
|
+
if (trailingSliceHasPragma(node))
|
|
1017
|
+
return true;
|
|
1018
|
+
if (trailingSliceHasPragma(node.getParent()))
|
|
1019
|
+
return true;
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
// =============================================================================
|
|
1023
|
+
// SMALL UTILITIES
|
|
1024
|
+
// =============================================================================
|
|
1025
|
+
function truncate(s, max) {
|
|
1026
|
+
if (s.length <= max)
|
|
1027
|
+
return s;
|
|
1028
|
+
return s.slice(0, max - 1) + '…';
|
|
1029
|
+
}
|
|
1030
|
+
//# sourceMappingURL=reference-resolver.js.map
|