@alint-js/core 0.1.3 → 0.1.5

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.
@@ -1,4 +1,4 @@
1
- import { h as SourceTarget, u as SourceFile } from "./types-DSvXUVZ7.mjs";
1
+ import { C as SourceTarget, g as SourceFile } from "./types-DM4idR4w.mjs";
2
2
  //#region src/core/languages/js/extract.d.ts
3
3
  declare function extractJsSourceTargets(file: SourceFile): SourceTarget[];
4
4
  //#endregion
@@ -1,2 +1,2 @@
1
- import { t as extractJsSourceTargets } from "./extract-CIWssxNK.mjs";
1
+ import { t as extractJsSourceTargets } from "./extract-BVbjWXMC.mjs";
2
2
  export { extractJsSourceTargets };
@@ -1,4 +1,4 @@
1
- import { b as RuleContext, z as ResolvedModel } from "./types-DKylUrAn.mjs";
1
+ import { b as RuleContext, z as ResolvedModel } from "./types-deBCDA4_.mjs";
2
2
  import { GenericSchema, InferOutput } from "valibot";
3
3
  import { JsonSchema } from "@valibot/to-json-schema";
4
4
  import { Message } from "@xsai/shared-chat";
@@ -0,0 +1,179 @@
1
+ //#region src/core/source/types.d.ts
2
+ interface BaseSourceFile {
3
+ contentHash: string;
4
+ language: string;
5
+ path: string;
6
+ }
7
+ /**
8
+ * Carried on a file target under `metadata.calls`, in source order.
9
+ *
10
+ * Every call in the file, including those outside any function: a consumer counting how often a
11
+ * name is used has to see the uses, not just the definitions.
12
+ */
13
+ interface CallSite extends SourceMetadataObject {
14
+ /** The last segment only: `a.b.helper()` and `helper()` both yield `helper`. */
15
+ name: string;
16
+ /** Absolute, into the file's text. */
17
+ range: SourceRange;
18
+ }
19
+ type ClassTarget = PlannedSourceTargetOfKind<'class'>;
20
+ type FileTarget = PlannedSourceTargetOfKind<'file'>;
21
+ /**
22
+ * Carried under `metadata.function`, so a consumer can fingerprint, count and classify a function
23
+ * without reaching for a parser of its own.
24
+ *
25
+ * Every producer of function targets should fill it in; a consumer still validates the expected
26
+ * domain fields when reading the recursive JSON metadata contract.
27
+ *
28
+ * Ranges are relative to the target's own `text`, not to the file, so a consumer can slice that
29
+ * text directly and never has to rebase.
30
+ */
31
+ interface FunctionInfo extends SourceMetadataObject {
32
+ /**
33
+ * Stricter than one statement: a body that is one `if` and its two returns is one statement and
34
+ * not one expression.
35
+ */
36
+ bodyIsSingleExpression: boolean;
37
+ /** Statements in the body, not counting comments. */
38
+ bodyStatements: number;
39
+ commentRanges: readonly SourceRange[];
40
+ /**
41
+ * The names the function declares: its own name, its parameters and its locals.
42
+ *
43
+ * A copy can rename what it declares and stay a copy, so this is the set an α-renaming
44
+ * fingerprint may replace. Everything else — callees, globals, types, properties — stays
45
+ * verbatim and is what tells two same-shaped functions apart.
46
+ */
47
+ declaredNames: readonly string[];
48
+ /** Reachable from outside its file, however the language spells it. */
49
+ exported: boolean;
50
+ /**
51
+ * Identifiers that MAY be renamed away, which is narrower than "every identifier token".
52
+ *
53
+ * Property, field and type names must not be here: replacing them collapses `entry.name` and
54
+ * `entry.size` into the same fingerprint, which is the mistake this set exists to avoid.
55
+ */
56
+ identifierRanges: readonly SourceRange[];
57
+ }
58
+ type FunctionTarget = PlannedSourceTargetOfKind<'function'>;
59
+ interface LanguageContext {
60
+ cwd: string;
61
+ languageOptions: Record<string, unknown>;
62
+ src: SourceRuntime;
63
+ }
64
+ interface LineRange {
65
+ endLine: number;
66
+ startLine: number;
67
+ }
68
+ interface PlannedSourceTarget {
69
+ file: BaseSourceFile;
70
+ identity: string;
71
+ kind: SourceTargetKind;
72
+ language: string;
73
+ loc?: SourceLocation;
74
+ metadata?: SourceTargetMetadata;
75
+ name?: string;
76
+ origin?: SourceTargetOrigin;
77
+ range?: SourceRange;
78
+ }
79
+ type PlannedSourceTargetOfKind<Kind extends SourceTargetKind> = Omit<PlannedSourceTarget, 'kind'> & {
80
+ kind: Kind;
81
+ };
82
+ interface ProcessedSource {
83
+ identity: string;
84
+ language?: string;
85
+ origin?: ProcessedSourceOrigin;
86
+ path: string;
87
+ text: string;
88
+ }
89
+ interface ProcessedSourceOrigin {
90
+ physicalPath: string;
91
+ range?: SourceRange;
92
+ virtualPath?: string;
93
+ }
94
+ interface ProcessorContext {
95
+ cwd: string;
96
+ options: Record<string, unknown>;
97
+ src: SourceRuntime;
98
+ }
99
+ interface ProcessorPostprocessContext extends ProcessorContext {
100
+ file: SourceFile;
101
+ processedSources: ProcessedSource[];
102
+ }
103
+ interface SourceExtractOptions {
104
+ /**
105
+ * Overrides the file's configured `language:` pin.
106
+ *
107
+ * For a caller walking a mixed tree, where the file's own config cannot know what each file is.
108
+ */
109
+ language?: string;
110
+ }
111
+ interface SourceFile extends BaseSourceFile {
112
+ lines: string[];
113
+ text: string;
114
+ }
115
+ interface SourceLocation {
116
+ end: SourcePosition;
117
+ start: SourcePosition;
118
+ }
119
+ interface SourceMetadataObject {
120
+ readonly [key: string]: SourceMetadataValue;
121
+ }
122
+ type SourceMetadataValue = boolean | null | number | readonly SourceMetadataValue[] | SourceMetadataObject | SourceRange | string;
123
+ interface SourcePosition {
124
+ column: number;
125
+ line: number;
126
+ }
127
+ interface SourceRange {
128
+ end: number;
129
+ start: number;
130
+ }
131
+ interface SourceRuntime {
132
+ /**
133
+ * Targets for any file, linted or not.
134
+ *
135
+ * Resolves the file's own config the way linting does, resolves its language from that config,
136
+ * and runs that language's extract. A rule handed `ProjectTarget.files`, or an index builder
137
+ * sweeping files outside the lint set, has no other way to parse them.
138
+ *
139
+ * An ignored file extracts to `[]` rather than throwing: sweeping broadly and skipping what the
140
+ * config excluded is the normal case, not an error.
141
+ *
142
+ * Only wired inside a run. A runtime built by `createSourceRuntime()` with no extractor throws,
143
+ * because there is no config to resolve the file against.
144
+ */
145
+ extract: (filePath: string, options?: SourceExtractOptions) => Promise<SourceTarget[]>;
146
+ getText: (file: SourceFile) => string;
147
+ readFile: (file: BaseSourceFile | string) => Promise<SourceFile>;
148
+ sliceLines: (file: SourceFile, range: LineRange) => SourceText;
149
+ sliceRange: (file: SourceFile, range: SourceRange) => SourceText;
150
+ }
151
+ interface SourceTarget {
152
+ file: SourceFile;
153
+ identity: string;
154
+ kind: SourceTargetKind;
155
+ language: string;
156
+ loc?: SourceLocation;
157
+ metadata?: SourceTargetMetadata;
158
+ name?: string;
159
+ origin?: SourceTargetOrigin;
160
+ range?: SourceRange;
161
+ text: string;
162
+ }
163
+ type SourceTargetKind = 'class' | 'file' | 'fragment' | 'function' | 'symbol' | (string & {});
164
+ type SourceTargetMetadata = Readonly<Record<string, SourceMetadataValue>>;
165
+ type SourceTargetOfKind<Kind extends SourceTargetKind> = Omit<SourceTarget, 'kind'> & {
166
+ kind: Kind;
167
+ };
168
+ interface SourceTargetOrigin {
169
+ physicalPath: string;
170
+ range?: SourceRange;
171
+ virtualPath?: string;
172
+ }
173
+ interface SourceText {
174
+ filePath: string;
175
+ loc: SourceLocation;
176
+ text: string;
177
+ }
178
+ //#endregion
179
+ export { SourceTarget as C, SourceTargetOrigin as D, SourceTargetOfKind as E, SourceText as O, SourceRuntime as S, SourceTargetMetadata as T, SourceLocation as _, FunctionInfo as a, SourcePosition as b, LineRange as c, ProcessedSource as d, ProcessedSourceOrigin as f, SourceFile as g, SourceExtractOptions as h, FileTarget as i, PlannedSourceTarget as l, ProcessorPostprocessContext as m, CallSite as n, FunctionTarget as o, ProcessorContext as p, ClassTarget as r, LanguageContext as s, BaseSourceFile as t, PlannedSourceTargetOfKind as u, SourceMetadataObject as v, SourceTargetKind as w, SourceRange as x, SourceMetadataValue as y };
@@ -1,4 +1,4 @@
1
- import { c as ProcessorContext, h as SourceTarget, i as LanguageContext, l as ProcessorPostprocessContext, m as SourceRuntime, n as FileTarget, o as ProcessedSource, p as SourceRange, r as FunctionTarget, t as ClassTarget, u as SourceFile } from "./types-DSvXUVZ7.mjs";
1
+ import { C as SourceTarget, S as SourceRuntime, d as ProcessedSource, g as SourceFile, i as FileTarget, l as PlannedSourceTarget, m as ProcessorPostprocessContext, o as FunctionTarget, p as ProcessorContext, r as ClassTarget, s as LanguageContext, t as BaseSourceFile, x as SourceRange } from "./types-DM4idR4w.mjs";
2
2
  import { GenericSchema, InferInput, InferOutput } from "valibot";
3
3
  //#region src/config/types.d.ts
4
4
  type ModelSize = 'large' | 'medium' | 'small';
@@ -121,6 +121,12 @@ interface AlintConfigItem {
121
121
  }
122
122
  interface AlintLinterOptions {
123
123
  noInlineConfig?: boolean;
124
+ /**
125
+ * How the run reports files it lints that no language claimed, so they were handled as plain
126
+ * text. Defaults to `'warn'`. An explicit `language: 'plaintext'` pin means plain text was the
127
+ * intent, so pinned files are never reported.
128
+ */
129
+ reportUnregisteredLanguages?: RuleSeverity;
124
130
  reportUnusedDisableDirectives?: RuleSeverity;
125
131
  }
126
132
  type Awaitable<T> = Promise<T> | T;
@@ -157,6 +163,20 @@ interface IgnoreConfig {
157
163
  interface LanguageDefinition {
158
164
  extensions?: readonly string[];
159
165
  extract: (file: SourceFile, context: LanguageContext) => Awaitable<SourceTarget[]>;
166
+ /**
167
+ * The language's id: what a config pins through `language:`, what a rule's `languages` lists, and
168
+ * what every target it extracts reports. The registry is keyed by it, so two packs cannot claim
169
+ * the same one.
170
+ *
171
+ * Use the identifier editors already use — `go`, `python`, `rust`, `typescript`, `plaintext` —
172
+ * rather than inventing a spelling. Anyone can register a language, so the set is open by
173
+ * definition and only a shared convention keeps one language from arriving under three names.
174
+ *
175
+ * NOTICE: the list is VS Code's. The Language Server Protocol shares it for the languages it
176
+ * covers, but its table has no entry for plain text, so `plaintext` comes from VS Code alone.
177
+ * https://code.visualstudio.com/docs/languages/identifiers
178
+ * https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
179
+ */
160
180
  name: string;
161
181
  }
162
182
  interface PluginDefinition<Rules extends Record<string, RuleDefinition<any>> = Record<string, RuleDefinition<any>>> {
@@ -169,10 +189,7 @@ interface ProcessorDefinition {
169
189
  postprocess?: (diagnostics: DiagnosticDescriptor[], context: ProcessorPostprocessContext) => Awaitable<DiagnosticDescriptor[]>;
170
190
  preprocess: (file: SourceFile, context: ProcessorContext) => Awaitable<ProcessedSource[]>;
171
191
  }
172
- interface ProjectFileEntry {
173
- contentHash: string;
174
- language: string;
175
- path: string;
192
+ interface ProjectFileEntry extends BaseSourceFile {
176
193
  targetCount: number;
177
194
  }
178
195
  interface ProjectTarget {
@@ -222,6 +239,15 @@ interface RuleDefinition<OptionsSchema extends RuleOptionsSchema = []> {
222
239
  /** Additional stable rule inputs, such as imported prompts, that invalidate cached results when changed. */
223
240
  cacheKey?: unknown;
224
241
  create: (context: RuleContext<RuleOptionsOutput<OptionsSchema>>) => RuleHandlers;
242
+ /**
243
+ * Which languages this rule reads. Omitting it opts out of extraction: the rule still receives
244
+ * file targets, but never the function and class targets a language produces.
245
+ *
246
+ * It does not choose how a file is parsed. A config's `language:` pin, or the extension, settles
247
+ * that before any rule is consulted. Directory and project targets ignore it too: they index a
248
+ * tree rather than come from a language.
249
+ */
250
+ languages?: RuleLanguages;
225
251
  model?: ModelRequirement;
226
252
  options?: OptionsSchema;
227
253
  }
@@ -236,6 +262,23 @@ interface RuleInferenceUsageRecord {
236
262
  ruleId?: string;
237
263
  totalTokens?: number;
238
264
  }
265
+ /**
266
+ * - `'any'` — every registered language, and never a failure. A rule that works from `FunctionInfo`
267
+ * alone wants this: a language pack the user installs later is covered without a new release.
268
+ * - A list of language ids — `LanguageDefinition.name` values such as `go` or `typescript`, never
269
+ * file extensions. The rule handles exactly these. Files of other languages are skipped
270
+ * rather than failed, so one plugin can carry rules for several languages behind one `files:`
271
+ * glob. If a listed language is not registered at all the run fails, because a rule scoped to a
272
+ * language nothing provides would otherwise skip every file in silence.
273
+ * - `{ ids: string[], skipMissing?: boolean }` — the same scoping, but an unregistered id can be
274
+ * skipped instead of failing. For a plugin whose rules span languages a given user may not have installed.
275
+ */
276
+ type RuleLanguages = 'any' | readonly string[] | {
277
+ /** Language ids this rule applies to. */
278
+ ids: readonly string[];
279
+ /** Whether to skip unregistered languages instead of failing. */
280
+ skipMissing?: boolean;
281
+ };
239
282
  type RuleOptionsInput<OptionsSchema extends RuleOptionsSchema> = { readonly [Index in keyof OptionsSchema]: InferInput<OptionsSchema[Index]>; };
240
283
  type RuleOptionsOutput<OptionsSchema extends RuleOptionsSchema> = { readonly [Index in keyof OptionsSchema]: InferOutput<OptionsSchema[Index]>; };
241
284
  type RuleOptionsSchema = readonly GenericSchema[];
@@ -260,6 +303,6 @@ interface RuleWithHandler {
260
303
  onTargetProject?: never;
261
304
  onTargetWith: (target: Target) => Awaitable<void>;
262
305
  }
263
- type Target = DirectoryTarget | ProjectTarget | SourceTarget;
306
+ type Target = DirectoryTarget | PlannedSourceTarget | ProjectTarget;
264
307
  //#endregion
265
308
  export { RuleWithHandler as A, ResolvedProvider as B, RuleInferenceUsageRecord as C, RuleRegistry as D, RuleOptionsSchema as E, AgentTool as F, RunnerStatsConfig as G, ProviderDefinition as H, AgentUsage as I, SetupConfig as K, ModelRequirement as L, AgentAdapter as M, AgentRequest as N, RuleSeverity as O, AgentResult as P, ResolveModelOptions as R, RuleHandlers as S, RuleOptionsOutput as T, ProviderType as U, ModelSize as V, RunnerConfig as W, ProjectTargetEntry as _, AlintLinterOptions as a, RuleContext as b, DiagnosticLocation as c, IgnoreConfig as d, LanguageDefinition as f, ProjectTarget as g, ProjectFileEntry as h, AlintConfigItem as i, Target as j, RuleSpecializedHandlers as k, DirectoryTarget as l, ProcessorDefinition as m, AlintConfigExtends as n, Awaitable as o, PluginDefinition as p, SetupModelDefinition as q, AlintConfigInput as r, DiagnosticDescriptor as s, AlintConfig as t, EnabledRule as u, RuleCacheConfig as v, RuleOptionsInput as w, RuleDefinition as x, RuleConfigEntry as y, ResolvedModel as z };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alint-js/core",
3
3
  "type": "module",
4
- "version": "0.1.3",
4
+ "version": "0.1.5",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",
@@ -33,6 +33,7 @@
33
33
  "@valibot/to-json-schema": "^1.7.1",
34
34
  "@xsai/generate-text": "^0.5.0-beta.7",
35
35
  "@xsai/shared-chat": "^0.5.0-beta.7",
36
+ "@xsai/stream-text": "^0.5.0-beta.7",
36
37
  "@xsai/tool": "^0.5.0-beta.7",
37
38
  "es-toolkit": "^1.49.0",
38
39
  "minimatch": "^10.2.5",
@@ -1,358 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { extname } from "node:path";
3
- import { clamp } from "es-toolkit";
4
- import { parseSync } from "oxc-parser";
5
- //#region src/core/source/runtime.ts
6
- function createSourceFile(path, text) {
7
- return {
8
- language: inferLanguage(path),
9
- lines: text.split(/\r?\n/),
10
- path,
11
- text
12
- };
13
- }
14
- function createSourceRuntime() {
15
- return {
16
- getText,
17
- readFile: async (filePath) => createSourceFile(filePath, await readFile(filePath, "utf8")),
18
- sliceLines,
19
- sliceRange
20
- };
21
- }
22
- function getText(target) {
23
- return target.text;
24
- }
25
- function sliceLines(file, range) {
26
- const startLine = clampLine(range.startLine, file.lines.length);
27
- const endLine = clampLine(range.endLine, file.lines.length);
28
- const orderedStartLine = Math.min(startLine, endLine);
29
- const orderedEndLine = Math.max(startLine, endLine);
30
- const text = file.lines.slice(orderedStartLine - 1, orderedEndLine).join("\n");
31
- return {
32
- filePath: file.path,
33
- loc: {
34
- end: {
35
- column: file.lines[orderedEndLine - 1]?.length ?? 0,
36
- line: orderedEndLine
37
- },
38
- start: {
39
- column: 0,
40
- line: orderedStartLine
41
- }
42
- },
43
- text
44
- };
45
- }
46
- function sliceRange(file, range) {
47
- const start = clampOffset(range.start, file.text.length);
48
- const end = clampOffset(range.end, file.text.length);
49
- const orderedStart = Math.min(start, end);
50
- const orderedEnd = Math.max(start, end);
51
- return {
52
- filePath: file.path,
53
- loc: {
54
- end: getPosition(file.text, orderedEnd),
55
- start: getPosition(file.text, orderedStart)
56
- },
57
- text: file.text.slice(orderedStart, orderedEnd)
58
- };
59
- }
60
- function clampLine(line, lineCount) {
61
- if (lineCount === 0) return 1;
62
- return clamp(Math.trunc(line), 1, lineCount);
63
- }
64
- function clampOffset(offset, textLength) {
65
- return clamp(Math.trunc(offset), 0, textLength);
66
- }
67
- function getPosition(text, offset) {
68
- let line = 1;
69
- let column = 0;
70
- let index = 0;
71
- while (index < offset) {
72
- const character = text[index];
73
- if (character === "\r") {
74
- if (text[index + 1] === "\n" && index + 1 < offset) index += 1;
75
- line += 1;
76
- column = 0;
77
- } else if (character === "\n") {
78
- line += 1;
79
- column = 0;
80
- } else column += 1;
81
- index += 1;
82
- }
83
- return {
84
- column,
85
- line
86
- };
87
- }
88
- function inferLanguage(path) {
89
- switch (extname(path)) {
90
- case ".cjs":
91
- case ".js":
92
- case ".jsx":
93
- case ".mjs": return "javascript";
94
- case ".cts":
95
- case ".mts":
96
- case ".ts":
97
- case ".tsx": return "typescript";
98
- default: return "unknown";
99
- }
100
- }
101
- //#endregion
102
- //#region src/core/languages/js/parser.ts
103
- function parseSync$1(filename, sourceText, options) {
104
- return parseSync(filename, sourceText, options);
105
- }
106
- //#endregion
107
- //#region src/core/languages/js/extract.ts
108
- function extractJsSourceTargets(file) {
109
- const result = parseSync$1(file.path, file.text, { sourceType: "module" });
110
- const state = {
111
- bindingNodes: /* @__PURE__ */ new Map(),
112
- exportedNodes: /* @__PURE__ */ new Set(),
113
- file,
114
- inferredNames: /* @__PURE__ */ new Map(),
115
- seenClasses: /* @__PURE__ */ new Set(),
116
- seenFunctions: /* @__PURE__ */ new Set(),
117
- targets: [],
118
- visited: /* @__PURE__ */ new Set()
119
- };
120
- collectModuleBindings(result.program, state);
121
- collectExportedBindings(result.program, state);
122
- visit(result.program, state);
123
- const sortedTargets = [...state.targets].sort((left, right) => (left.range?.start ?? 0) - (right.range?.start ?? 0));
124
- return [createFileTarget(file), ...withStableIdentities(sortedTargets)];
125
- }
126
- function addClassTarget(node, state) {
127
- if (state.seenClasses.has(node)) return;
128
- const target = createClassTarget(node, state);
129
- if (!target) return;
130
- state.seenClasses.add(node);
131
- state.targets.push(target);
132
- }
133
- function addFunctionTarget(node, state) {
134
- if (state.seenFunctions.has(node)) return;
135
- const target = createFunctionTarget(node, state);
136
- if (!target) return;
137
- state.seenFunctions.add(node);
138
- state.targets.push(target);
139
- }
140
- function asAstNode(value) {
141
- return isAstNode(value) ? value : void 0;
142
- }
143
- function collectDeclarationBindings(node, state) {
144
- const name = getNodeName(node);
145
- if (name && (isClassNode(node) || isFunctionNode(node))) {
146
- state.bindingNodes.set(name, node);
147
- return;
148
- }
149
- if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
150
- for (const declarator of node.declarations) {
151
- if (!isAstNode(declarator)) continue;
152
- const id = asAstNode(declarator.id);
153
- const initializer = asAstNode(declarator.init);
154
- if (typeof id?.name === "string" && initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.bindingNodes.set(id.name, initializer);
155
- }
156
- }
157
- function collectExportedBindings(node, state) {
158
- if (!node) return;
159
- if (Array.isArray(node)) {
160
- for (const child of node) collectExportedBindings(child, state);
161
- return;
162
- }
163
- if (node.type === "ExportNamedDeclaration" && !node.source && Array.isArray(node.specifiers)) for (const specifier of node.specifiers) {
164
- if (!isAstNode(specifier) || specifier.type !== "ExportSpecifier") continue;
165
- const local = asAstNode(specifier.local);
166
- if (typeof local?.name === "string") markExportedBinding(local.name, state);
167
- }
168
- else if (node.type === "ExportDefaultDeclaration") {
169
- const declaration = asAstNode(node.declaration);
170
- if (typeof declaration?.name === "string") markExportedBinding(declaration.name, state);
171
- }
172
- for (const value of Object.values(node)) if (Array.isArray(value)) collectExportedBindings(value.filter(isAstNode), state);
173
- else if (isAstNode(value)) collectExportedBindings(value, state);
174
- }
175
- function collectModuleBindings(program, state) {
176
- if (!Array.isArray(program.body)) return;
177
- for (const statement of program.body) {
178
- if (!isAstNode(statement)) continue;
179
- const declaration = isExportDeclaration(statement) ? asAstNode(statement.declaration) : statement;
180
- if (declaration) collectDeclarationBindings(declaration, state);
181
- }
182
- }
183
- function createClassTarget(node, state) {
184
- const range = getRange(node);
185
- if (!range) return;
186
- const source = sliceRange(state.file, range);
187
- const name = getNodeName(node) ?? state.inferredNames.get(node);
188
- return {
189
- file: state.file,
190
- identity: createRangeIdentity("class", name, range),
191
- kind: "class",
192
- language: state.file.language,
193
- loc: source.loc,
194
- metadata: { exported: isExportedTarget(node, state) },
195
- name,
196
- origin: {
197
- physicalPath: state.file.path,
198
- range
199
- },
200
- range,
201
- text: source.text
202
- };
203
- }
204
- function createFileTarget(file) {
205
- return {
206
- file,
207
- identity: "file",
208
- kind: "file",
209
- language: file.language,
210
- origin: { physicalPath: file.path },
211
- text: file.text
212
- };
213
- }
214
- function createFunctionTarget(node, state) {
215
- const range = getRange(node);
216
- if (!range) return;
217
- const source = sliceRange(state.file, range);
218
- const functionNode = node.type === "MethodDefinition" ? asAstNode(node.value) : node;
219
- const name = getNodeName(node) ?? getNodeName(functionNode) ?? state.inferredNames.get(node);
220
- return {
221
- file: state.file,
222
- identity: createRangeIdentity("function", name, range),
223
- kind: "function",
224
- language: state.file.language,
225
- loc: source.loc,
226
- metadata: {
227
- async: functionNode?.async === true,
228
- exported: isExportedTarget(node, state)
229
- },
230
- name,
231
- origin: {
232
- physicalPath: state.file.path,
233
- range
234
- },
235
- range,
236
- text: source.text
237
- };
238
- }
239
- function createRangeIdentity(kind, name, range) {
240
- return `${kind}:${name ?? "anonymous"}:${range.start}:${range.end}`;
241
- }
242
- function createSemanticIdentity(target) {
243
- if (target.kind !== "class" && target.kind !== "function" || !target.name) return;
244
- return `${target.kind}:${target.name}`;
245
- }
246
- function getNodeName(node) {
247
- if (!node) return;
248
- if (typeof node.name === "string") return node.name;
249
- const id = asAstNode(node.id);
250
- if (typeof id?.name === "string") return id.name;
251
- const key = asAstNode(node.key);
252
- if (typeof key?.name === "string") return key.name;
253
- if (typeof key?.value === "string" || typeof key?.value === "number") return String(key.value);
254
- }
255
- function getRange(node) {
256
- if (typeof node.start === "number" && typeof node.end === "number") return {
257
- end: node.end,
258
- start: node.start
259
- };
260
- if (Array.isArray(node.range) && node.range.length === 2) return {
261
- end: node.range[1],
262
- start: node.range[0]
263
- };
264
- }
265
- function inferChildName(parent, key, child, state) {
266
- if (state.inferredNames.has(child)) return;
267
- if (parent.type === "VariableDeclarator" && key === "init") {
268
- const id = asAstNode(parent.id);
269
- if (typeof id?.name === "string") state.inferredNames.set(child, id.name);
270
- }
271
- if (parent.type === "Property" && key === "value") {
272
- const name = getNodeName(parent);
273
- if (name) state.inferredNames.set(child, name);
274
- }
275
- }
276
- function isAstNode(value) {
277
- return typeof value === "object" && value !== null && typeof value.type === "string";
278
- }
279
- function isClassNode(node) {
280
- return node.type === "ClassDeclaration" || node.type === "ClassExpression";
281
- }
282
- function isExportDeclaration(node) {
283
- return node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration";
284
- }
285
- function isExportedTarget(node, state) {
286
- return state.exportedNodes.has(node);
287
- }
288
- function isFunctionNode(node) {
289
- return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
290
- }
291
- function markExportedBinding(name, state) {
292
- const bindingNode = state.bindingNodes.get(name);
293
- if (bindingNode) state.exportedNodes.add(bindingNode);
294
- }
295
- function markExportedDeclaration(node, state) {
296
- state.exportedNodes.add(node);
297
- if (node.type !== "VariableDeclaration" || !Array.isArray(node.declarations)) return;
298
- for (const declarator of node.declarations) {
299
- if (!isAstNode(declarator)) continue;
300
- const initializer = asAstNode(declarator.init);
301
- if (initializer && (isClassNode(initializer) || isFunctionNode(initializer))) state.exportedNodes.add(initializer);
302
- }
303
- }
304
- function visit(node, state) {
305
- if (!node) return;
306
- if (Array.isArray(node)) {
307
- for (const child of node) visit(child, state);
308
- return;
309
- }
310
- if (state.visited.has(node)) return;
311
- state.visited.add(node);
312
- if (node.type === "ExportDefaultDeclaration" || node.type === "ExportNamedDeclaration") {
313
- if (node.declaration) {
314
- markExportedDeclaration(node.declaration, state);
315
- visit(node.declaration, state);
316
- }
317
- visitChildren(node, state, /* @__PURE__ */ new Set(["declaration"]));
318
- return;
319
- }
320
- if (isClassNode(node)) addClassTarget(node, state);
321
- else if (isFunctionNode(node) || node.type === "MethodDefinition") {
322
- addFunctionTarget(node, state);
323
- const methodValue = node.type === "MethodDefinition" ? asAstNode(node.value) : void 0;
324
- if (methodValue) state.seenFunctions.add(methodValue);
325
- }
326
- visitChildren(node, state);
327
- }
328
- function visitChildren(node, state, skippedKeys = /* @__PURE__ */ new Set()) {
329
- for (const [key, value] of Object.entries(node)) {
330
- if (skippedKeys.has(key) || key === "type" || key === "start" || key === "end" || key === "range") continue;
331
- if (Array.isArray(value)) {
332
- for (const child of value) if (isAstNode(child)) {
333
- inferChildName(node, key, child, state);
334
- visit(child, state);
335
- }
336
- } else if (isAstNode(value)) {
337
- inferChildName(node, key, value, state);
338
- visit(value, state);
339
- }
340
- }
341
- }
342
- function withStableIdentities(targets) {
343
- const semanticIdentityCounts = /* @__PURE__ */ new Map();
344
- for (const target of targets) {
345
- const semanticIdentity = createSemanticIdentity(target);
346
- if (semanticIdentity) semanticIdentityCounts.set(semanticIdentity, (semanticIdentityCounts.get(semanticIdentity) ?? 0) + 1);
347
- }
348
- return targets.map((target) => {
349
- const semanticIdentity = createSemanticIdentity(target);
350
- if (!semanticIdentity || semanticIdentityCounts.get(semanticIdentity) !== 1) return target;
351
- return {
352
- ...target,
353
- identity: semanticIdentity
354
- };
355
- });
356
- }
357
- //#endregion
358
- export { sliceRange as a, sliceLines as i, createSourceFile as n, createSourceRuntime as r, extractJsSourceTargets as t };