@alint-js/core 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/dist/agent.d.mts +1 -1
- package/dist/{define-8BzWDcPs.d.mts → define-CJ2QJPZ8.d.mts} +1 -1
- package/dist/dsl.d.mts +2 -2
- package/dist/extract-BVbjWXMC.mjs +676 -0
- package/dist/index.d.mts +109 -7
- package/dist/index.mjs +867 -384
- package/dist/languages-js.d.mts +1 -1
- package/dist/languages-js.mjs +1 -1
- package/dist/structured-output.d.mts +1 -1
- package/dist/types-DM4idR4w.d.mts +179 -0
- package/dist/{types-DKylUrAn.d.mts → types-deBCDA4_.d.mts} +49 -6
- package/package.json +2 -1
- package/dist/extract-CIWssxNK.mjs +0 -358
- package/dist/types-DSvXUVZ7.d.mts +0 -86
package/dist/languages-js.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
package/dist/languages-js.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as extractJsSourceTargets } from "./extract-
|
|
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-
|
|
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 {
|
|
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 |
|
|
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.
|
|
4
|
+
"version": "0.2.0",
|
|
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 };
|