svelte-docinfo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/analyze-core.d.ts +1386 -0
- package/dist/analyze-core.d.ts.map +1 -0
- package/dist/analyze-core.js +324 -0
- package/dist/analyze.d.ts +137 -0
- package/dist/analyze.d.ts.map +1 -0
- package/dist/analyze.js +148 -0
- package/dist/cli.d.ts +102 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/concurrency.d.ts +38 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +57 -0
- package/dist/declaration-build.d.ts +164 -0
- package/dist/declaration-build.d.ts.map +1 -0
- package/dist/declaration-build.js +15 -0
- package/dist/declaration-helpers.d.ts +169 -0
- package/dist/declaration-helpers.d.ts.map +1 -0
- package/dist/declaration-helpers.js +239 -0
- package/dist/dep-resolver.d.ts +166 -0
- package/dist/dep-resolver.d.ts.map +1 -0
- package/dist/dep-resolver.js +144 -0
- package/dist/diagnostics.d.ts +650 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +377 -0
- package/dist/discovery.d.ts +115 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +126 -0
- package/dist/exports.d.ts +105 -0
- package/dist/exports.d.ts.map +1 -0
- package/dist/exports.js +274 -0
- package/dist/files.d.ts +77 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +95 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/log.d.ts +32 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +11 -0
- package/dist/logo.d.ts +8 -0
- package/dist/logo.d.ts.map +1 -0
- package/dist/logo.js +9 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +18 -0
- package/dist/paths.d.ts +35 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +34 -0
- package/dist/postprocess.d.ts +145 -0
- package/dist/postprocess.d.ts.map +1 -0
- package/dist/postprocess.js +305 -0
- package/dist/session.d.ts +248 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +496 -0
- package/dist/source-config.d.ts +330 -0
- package/dist/source-config.d.ts.map +1 -0
- package/dist/source-config.js +356 -0
- package/dist/source.d.ts +126 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +68 -0
- package/dist/svelte.d.ts +179 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +898 -0
- package/dist/tsdoc.d.ts +145 -0
- package/dist/tsdoc.d.ts.map +1 -0
- package/dist/tsdoc.js +262 -0
- package/dist/types.d.ts +3381 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +588 -0
- package/dist/typescript-exports.d.ts +91 -0
- package/dist/typescript-exports.d.ts.map +1 -0
- package/dist/typescript-exports.js +656 -0
- package/dist/typescript-extract-class.d.ts +29 -0
- package/dist/typescript-extract-class.d.ts.map +1 -0
- package/dist/typescript-extract-class.js +251 -0
- package/dist/typescript-extract-function.d.ts +47 -0
- package/dist/typescript-extract-function.d.ts.map +1 -0
- package/dist/typescript-extract-function.js +108 -0
- package/dist/typescript-extract-shared.d.ts +166 -0
- package/dist/typescript-extract-shared.d.ts.map +1 -0
- package/dist/typescript-extract-shared.js +528 -0
- package/dist/typescript-extract-type-properties.d.ts +34 -0
- package/dist/typescript-extract-type-properties.d.ts.map +1 -0
- package/dist/typescript-extract-type-properties.js +241 -0
- package/dist/typescript-extract-type.d.ts +48 -0
- package/dist/typescript-extract-type.d.ts.map +1 -0
- package/dist/typescript-extract-type.js +238 -0
- package/dist/typescript-program.d.ts +211 -0
- package/dist/typescript-program.d.ts.map +1 -0
- package/dist/typescript-program.js +290 -0
- package/dist/vite.d.ts +116 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +418 -0
- package/package.json +131 -0
- package/virtual-svelte-docinfo.d.ts +49 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for the per-declaration extractors in `typescript-extract-*.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Holds helpers used across function, type, and class extraction: signature
|
|
5
|
+
* parameter extraction, overload detection, generic parsing, modifier
|
|
6
|
+
* extraction, location reporting, intersection-property filtering, and runes
|
|
7
|
+
* detection.
|
|
8
|
+
*
|
|
9
|
+
* @see `typescript-extract-function.ts`, `typescript-extract-type.ts`,
|
|
10
|
+
* `typescript-extract-class.ts` for the per-declaration extractors that
|
|
11
|
+
* build on these helpers
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import ts from 'typescript';
|
|
16
|
+
import {} from './diagnostics.js';
|
|
17
|
+
import { applyToDeclaration, parseComment } from './tsdoc.js';
|
|
18
|
+
import {} from './typescript-program.js';
|
|
19
|
+
/**
|
|
20
|
+
* Infer declaration kind from symbol and node.
|
|
21
|
+
*
|
|
22
|
+
* Maps TypeScript constructs to `DeclarationKind`:
|
|
23
|
+
* - Classes → `'class'`
|
|
24
|
+
* - Functions (declarations, expressions, arrows) → `'function'`
|
|
25
|
+
* - Interfaces → `'interface'`
|
|
26
|
+
* - Type aliases → `'type'`
|
|
27
|
+
* - Enums (regular and const) → `'enum'`
|
|
28
|
+
* - Variables → `'variable'` (unless function-valued → `'function'`)
|
|
29
|
+
*
|
|
30
|
+
* Note: namespace re-exports (`export * as ns from './x'`) have no inline
|
|
31
|
+
* declaration form in TypeScript and are caught upstream in `analyzeExports`
|
|
32
|
+
* via `classifyNamespaceReExport`. They never reach this function. A direct
|
|
33
|
+
* call here on a `ValueModule` symbol would fall through to `'variable'` and
|
|
34
|
+
* leak `typeof import("/abs/path")` into the output — keep the namespace
|
|
35
|
+
* dispatch in `analyzeExports`.
|
|
36
|
+
*/
|
|
37
|
+
export const inferDeclarationKind = (symbol, node) => {
|
|
38
|
+
// Check symbol flags
|
|
39
|
+
if (symbol.flags & ts.SymbolFlags.Class)
|
|
40
|
+
return 'class';
|
|
41
|
+
if (symbol.flags & ts.SymbolFlags.Function)
|
|
42
|
+
return 'function';
|
|
43
|
+
if (symbol.flags & ts.SymbolFlags.Interface)
|
|
44
|
+
return 'interface';
|
|
45
|
+
if (symbol.flags & ts.SymbolFlags.TypeAlias)
|
|
46
|
+
return 'type';
|
|
47
|
+
if (symbol.flags & ts.SymbolFlags.Enum)
|
|
48
|
+
return 'enum';
|
|
49
|
+
if (symbol.flags & ts.SymbolFlags.ConstEnum)
|
|
50
|
+
return 'enum';
|
|
51
|
+
// Check node kind
|
|
52
|
+
if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node))
|
|
53
|
+
return 'function';
|
|
54
|
+
if (ts.isClassDeclaration(node))
|
|
55
|
+
return 'class';
|
|
56
|
+
if (ts.isInterfaceDeclaration(node))
|
|
57
|
+
return 'interface';
|
|
58
|
+
if (ts.isTypeAliasDeclaration(node))
|
|
59
|
+
return 'type';
|
|
60
|
+
if (ts.isEnumDeclaration(node))
|
|
61
|
+
return 'enum';
|
|
62
|
+
if (ts.isVariableDeclaration(node)) {
|
|
63
|
+
// Check if it's a function-valued variable
|
|
64
|
+
const init = node.initializer;
|
|
65
|
+
if (init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
|
|
66
|
+
return 'function';
|
|
67
|
+
}
|
|
68
|
+
return 'variable';
|
|
69
|
+
}
|
|
70
|
+
return 'variable';
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Extract parameters from a TypeScript signature with TSDoc descriptions and default values.
|
|
74
|
+
*
|
|
75
|
+
* Shared helper for extracting parameter information from both standalone functions
|
|
76
|
+
* and class methods/constructors.
|
|
77
|
+
*
|
|
78
|
+
* @param sig - the TypeScript signature to extract parameters from
|
|
79
|
+
* @param checker - TypeScript type checker for type resolution
|
|
80
|
+
* @param tsdocParams - record of parameter names to TSDoc descriptions (from `TsdocParsedComment.params`)
|
|
81
|
+
* @returns array of `ParameterJson` objects
|
|
82
|
+
*/
|
|
83
|
+
export const extractSignatureParameters = (sig, checker, tsdocParams) => {
|
|
84
|
+
return sig.parameters.map((param) => {
|
|
85
|
+
const paramDecl = param.valueDeclaration;
|
|
86
|
+
// Get type - use declaration location if available, otherwise get declared type
|
|
87
|
+
let typeString = 'unknown';
|
|
88
|
+
if (paramDecl) {
|
|
89
|
+
const paramType = checker.getTypeOfSymbolAtLocation(param, paramDecl);
|
|
90
|
+
typeString = checker.typeToString(paramType);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const paramType = checker.getDeclaredTypeOfSymbol(param);
|
|
94
|
+
typeString = checker.typeToString(paramType);
|
|
95
|
+
}
|
|
96
|
+
// Get TSDoc description for this parameter
|
|
97
|
+
const description = tsdocParams?.[param.name];
|
|
98
|
+
// Extract default value from AST
|
|
99
|
+
let defaultValue;
|
|
100
|
+
if (paramDecl && ts.isParameter(paramDecl) && paramDecl.initializer) {
|
|
101
|
+
defaultValue = paramDecl.initializer.getText();
|
|
102
|
+
}
|
|
103
|
+
const optional = !!(paramDecl && ts.isParameter(paramDecl) && paramDecl.questionToken);
|
|
104
|
+
const rest = !!(paramDecl && ts.isParameter(paramDecl) && paramDecl.dotDotDotToken);
|
|
105
|
+
return {
|
|
106
|
+
name: param.name,
|
|
107
|
+
type: typeString,
|
|
108
|
+
optional,
|
|
109
|
+
rest,
|
|
110
|
+
description,
|
|
111
|
+
defaultValue,
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Emit `unknown_param` warnings for `@param` keys that don't reference a real
|
|
117
|
+
* parameter. Catches typos (`@param argz` for `args`) and stale doc after a
|
|
118
|
+
* rename. The description is dropped silently by `extractSignatureParameters`;
|
|
119
|
+
* this surfaces the drop without halting.
|
|
120
|
+
*
|
|
121
|
+
* @internal Helper for `extractOverloads` and other `@param`-extracting sites.
|
|
122
|
+
*/
|
|
123
|
+
const validateParamKeys = (tsdocParams, parameters, declNode, functionName, diagnostics) => {
|
|
124
|
+
if (!tsdocParams)
|
|
125
|
+
return;
|
|
126
|
+
const known = new Set(parameters.map((p) => p.name));
|
|
127
|
+
for (const key of Object.keys(tsdocParams)) {
|
|
128
|
+
if (!known.has(key)) {
|
|
129
|
+
const loc = getNodeLocation(declNode);
|
|
130
|
+
diagnostics.push({
|
|
131
|
+
kind: 'unknown_param',
|
|
132
|
+
file: loc.file,
|
|
133
|
+
line: loc.line,
|
|
134
|
+
column: loc.column,
|
|
135
|
+
message: `@param "${key}" on "${functionName}" doesn't match any parameter (typo or stale doc?)`,
|
|
136
|
+
severity: 'warning',
|
|
137
|
+
paramName: key,
|
|
138
|
+
functionName,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Collect symbol-scope JSDoc tags present on a parsed comment.
|
|
145
|
+
*
|
|
146
|
+
* Symbol-scope tags describe the function as a whole and belong on the
|
|
147
|
+
* primary signature's JSDoc (which feeds the parent declaration). Used by
|
|
148
|
+
* `extractOverloads` to detect tags misplaced on non-primary overloads.
|
|
149
|
+
*
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
const collectSymbolScopeTags = (tsdoc) => {
|
|
153
|
+
const found = [];
|
|
154
|
+
if (tsdoc.examples?.length)
|
|
155
|
+
found.push('example');
|
|
156
|
+
if (tsdoc.deprecatedMessage !== undefined)
|
|
157
|
+
found.push('deprecated');
|
|
158
|
+
if (tsdoc.since)
|
|
159
|
+
found.push('since');
|
|
160
|
+
if (tsdoc.seeAlso?.length)
|
|
161
|
+
found.push('see');
|
|
162
|
+
if (tsdoc.throws?.length)
|
|
163
|
+
found.push('throws');
|
|
164
|
+
if (tsdoc.mutates && Object.keys(tsdoc.mutates).length > 0)
|
|
165
|
+
found.push('mutates');
|
|
166
|
+
if (tsdoc.defaultValue !== undefined)
|
|
167
|
+
found.push('default');
|
|
168
|
+
if (tsdoc.nodocs)
|
|
169
|
+
found.push('nodocs');
|
|
170
|
+
return found;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Extract all public overload signatures for a function.
|
|
174
|
+
*
|
|
175
|
+
* Each overload gets its own typeSignature, parameters, returnType, and
|
|
176
|
+
* per-overload JSDoc if available. The implementation signature is excluded
|
|
177
|
+
* (TypeScript's `getCallSignatures()` already omits it).
|
|
178
|
+
*
|
|
179
|
+
* Per-overload `@param` descriptions flow through to that overload's
|
|
180
|
+
* `parameters[i].description`. Per-overload `@returns` populates
|
|
181
|
+
* `returnDescription`. These are signature-scope: each overload may
|
|
182
|
+
* describe its own parameters and return value distinctly.
|
|
183
|
+
*
|
|
184
|
+
* Symbol-scope tags (`@example`, `@deprecated`, `@since`, `@see`, `@throws`,
|
|
185
|
+
* `@mutates`) describe the function as a whole and belong on the parent
|
|
186
|
+
* declaration. The primary overload — the one whose JSDoc text matches the
|
|
187
|
+
* parent's `docComment` — already feeds the parent's symbol-level extraction,
|
|
188
|
+
* so its symbol-scope tags reach the parent through that path. On non-primary
|
|
189
|
+
* overloads, symbol-scope tags would otherwise be silently dropped from
|
|
190
|
+
* output; this function emits a `misplaced_tag` warning instead, pointing the
|
|
191
|
+
* author at the primary signature.
|
|
192
|
+
*
|
|
193
|
+
* @param signatures - all call signatures from the type checker
|
|
194
|
+
* @param checker - TypeScript type checker
|
|
195
|
+
* @param parentTsdoc - parsed JSDoc of the parent declaration (for primary-signature detection)
|
|
196
|
+
* @param parentName - parent function/method name (for diagnostic messages)
|
|
197
|
+
* @param diagnostics - diagnostics collector for non-fatal issues
|
|
198
|
+
* @returns array of overload info objects
|
|
199
|
+
*/
|
|
200
|
+
const extractOverloads = (signatures, checker, parentTsdoc, parentName, diagnostics) => {
|
|
201
|
+
return signatures.map((sig) => {
|
|
202
|
+
const decl = sig.getDeclaration();
|
|
203
|
+
const sourceFile = decl.getSourceFile();
|
|
204
|
+
const tsdoc = parseComment(decl, sourceFile);
|
|
205
|
+
const typeSignature = checker.signatureToString(sig);
|
|
206
|
+
const returnType = checker.typeToString(checker.getReturnTypeOfSignature(sig));
|
|
207
|
+
const parameters = extractSignatureParameters(sig, checker, tsdoc?.params);
|
|
208
|
+
validateParamKeys(tsdoc?.params, parameters, decl, parentName, diagnostics);
|
|
209
|
+
const overload = { typeSignature, parameters, returnType };
|
|
210
|
+
if (tsdoc?.text) {
|
|
211
|
+
overload.docComment = tsdoc.text;
|
|
212
|
+
}
|
|
213
|
+
if (tsdoc?.returns) {
|
|
214
|
+
overload.returnDescription = tsdoc.returns;
|
|
215
|
+
}
|
|
216
|
+
// Extract per-overload generic type parameters
|
|
217
|
+
if (ts.isFunctionLike(decl) && decl.typeParameters?.length) {
|
|
218
|
+
overload.genericParams = decl.typeParameters.map(parseGenericParam);
|
|
219
|
+
}
|
|
220
|
+
// Detect primary overload by matching JSDoc text against the parent's.
|
|
221
|
+
// The TS API resolves the parent declaration's JSDoc by walking from the
|
|
222
|
+
// implementation node to the first overload signature with JSDoc; that
|
|
223
|
+
// signature is the "primary" — its symbol-scope tags already reach the
|
|
224
|
+
// parent through symbol-level extraction. Non-primary overloads with
|
|
225
|
+
// symbol-scope tags would silently lose them; surface as warnings instead.
|
|
226
|
+
const isPrimary = tsdoc?.text !== undefined &&
|
|
227
|
+
parentTsdoc?.text !== undefined &&
|
|
228
|
+
tsdoc.text === parentTsdoc.text;
|
|
229
|
+
if (!isPrimary && tsdoc) {
|
|
230
|
+
const misplaced = collectSymbolScopeTags(tsdoc);
|
|
231
|
+
if (misplaced.length > 0) {
|
|
232
|
+
const loc = getNodeLocation(decl);
|
|
233
|
+
for (const tagName of misplaced) {
|
|
234
|
+
diagnostics.push({
|
|
235
|
+
kind: 'misplaced_tag',
|
|
236
|
+
file: loc.file,
|
|
237
|
+
line: loc.line,
|
|
238
|
+
column: loc.column,
|
|
239
|
+
message: `@${tagName} on non-primary overload of "${parentName}" — place it on the primary signature's JSDoc instead (symbol-scope tags describe the function as a whole)`,
|
|
240
|
+
severity: 'warning',
|
|
241
|
+
tagName,
|
|
242
|
+
functionName: parentName,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return overload;
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Populate the callable fields of a declaration or member from its call/construct
|
|
252
|
+
* signatures: `typeSignature`, `parameters`, `overloads`, and (unless
|
|
253
|
+
* `includeReturn` is false) `returnType` / `returnDescription`.
|
|
254
|
+
*
|
|
255
|
+
* The shared core of every named-callable extractor — standalone functions,
|
|
256
|
+
* interface methods, class methods/constructors, and type-alias function
|
|
257
|
+
* properties. Callers differ in how they obtain `signatures` (symbol type,
|
|
258
|
+
* constructor declarations, property call signatures) and in their own
|
|
259
|
+
* try/catch + diagnostic kind, so those stay at the callsite; this captures
|
|
260
|
+
* only the identical projection from a resolved signature list onto the build
|
|
261
|
+
* target. No-op when `signatures` is empty.
|
|
262
|
+
*
|
|
263
|
+
* @param target - declaration or member build object (mutated)
|
|
264
|
+
* @param signatures - public call/construct signatures (`signatures[0]` is primary)
|
|
265
|
+
* @param tsdoc - parsed TSDoc for the target (supplies `@param`/`@returns`)
|
|
266
|
+
* @param paramValidationNode - node `validateParamKeys` reports `unknown_param` against
|
|
267
|
+
* @param name - target name, for diagnostic messages
|
|
268
|
+
* @param includeReturn - set `false` for constructors (no return type/description)
|
|
269
|
+
* @mutates target - sets typeSignature, parameters, overloads, returnType, returnDescription
|
|
270
|
+
* @mutates diagnostics - via `validateParamKeys` / `extractOverloads`
|
|
271
|
+
*/
|
|
272
|
+
export const populateCallableMember = (target, signatures, checker, tsdoc, paramValidationNode, name, diagnostics, includeReturn = true) => {
|
|
273
|
+
if (signatures.length === 0)
|
|
274
|
+
return;
|
|
275
|
+
const sig = signatures[0];
|
|
276
|
+
target.typeSignature = checker.signatureToString(sig);
|
|
277
|
+
if (includeReturn) {
|
|
278
|
+
target.returnType = checker.typeToString(checker.getReturnTypeOfSignature(sig));
|
|
279
|
+
if (tsdoc?.returns)
|
|
280
|
+
target.returnDescription = tsdoc.returns;
|
|
281
|
+
}
|
|
282
|
+
target.parameters = extractSignatureParameters(sig, checker, tsdoc?.params);
|
|
283
|
+
validateParamKeys(tsdoc?.params, target.parameters, paramValidationNode, name, diagnostics);
|
|
284
|
+
if (signatures.length > 1) {
|
|
285
|
+
target.overloads = extractOverloads(signatures, checker, tsdoc, name, diagnostics);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* Check whether all declarations of a property symbol are in external source files.
|
|
290
|
+
* Properties with no declarations (synthesized) are considered non-external.
|
|
291
|
+
*/
|
|
292
|
+
const isExternalProperty = (prop, isExternalFile) => {
|
|
293
|
+
const decls = prop.getDeclarations();
|
|
294
|
+
if (!decls?.length)
|
|
295
|
+
return false;
|
|
296
|
+
return decls.every((d) => isExternalFile(d.getSourceFile()));
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* Determine whether an intersection branch refers to a type whose declarations
|
|
300
|
+
* all live in external files (e.g., `HTMLAttributes<HTMLDivElement>` from svelte's
|
|
301
|
+
* d.ts). Inline object-literal branches and unrecognized node shapes return false
|
|
302
|
+
* (treated as local).
|
|
303
|
+
*/
|
|
304
|
+
export const isExternalIntersectionBranch = (branchNode, checker, isExternalFile) => {
|
|
305
|
+
if (!ts.isTypeReferenceNode(branchNode) && !ts.isIndexedAccessTypeNode(branchNode)) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
const branchType = checker.getTypeAtLocation(branchNode);
|
|
309
|
+
const sym = branchType.aliasSymbol ?? branchType.symbol;
|
|
310
|
+
const decls = sym.getDeclarations();
|
|
311
|
+
if (!decls?.length)
|
|
312
|
+
return false;
|
|
313
|
+
return decls.every((d) => isExternalFile(d.getSourceFile()));
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Resolve the `IntersectionTypeNode` for a type node, walking through type
|
|
317
|
+
* alias indirection. Returns `undefined` when the underlying type is an
|
|
318
|
+
* intersection but no AST node is recoverable (rare — synthesized intersections).
|
|
319
|
+
*/
|
|
320
|
+
export const resolveIntersectionTypeNode = (type, typeNode) => {
|
|
321
|
+
if (ts.isIntersectionTypeNode(typeNode))
|
|
322
|
+
return typeNode;
|
|
323
|
+
const aliasDecl = type.aliasSymbol?.declarations?.[0];
|
|
324
|
+
if (aliasDecl &&
|
|
325
|
+
ts.isTypeAliasDeclaration(aliasDecl) &&
|
|
326
|
+
ts.isIntersectionTypeNode(aliasDecl.type)) {
|
|
327
|
+
return aliasDecl.type;
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* Filter intersection type properties to exclude those from external sources,
|
|
333
|
+
* and extract type names for branches whose properties are all external.
|
|
334
|
+
*
|
|
335
|
+
* Returns `null` for non-intersection types (caller should use unfiltered properties).
|
|
336
|
+
* For intersection types, returns filtered properties and external type names.
|
|
337
|
+
*
|
|
338
|
+
* Per-property filtering works through utility types (Partial, Pick, Readonly)
|
|
339
|
+
* because TypeScript preserves original declaration sources on mapped type properties.
|
|
340
|
+
*/
|
|
341
|
+
export const filterIntersectionProperties = (type, typeNode, checker, isExternalFile) => {
|
|
342
|
+
if (!type.isIntersection())
|
|
343
|
+
return null;
|
|
344
|
+
const properties = type
|
|
345
|
+
.getProperties()
|
|
346
|
+
.filter((prop) => !isExternalProperty(prop, isExternalFile));
|
|
347
|
+
// Resolve through type aliases (e.g., svelte2tsx $$ComponentProps) to find
|
|
348
|
+
// the IntersectionTypeNode for `intersects` extraction.
|
|
349
|
+
const intersectionNode = resolveIntersectionTypeNode(type, typeNode);
|
|
350
|
+
const intersectionTypes = [];
|
|
351
|
+
if (intersectionNode) {
|
|
352
|
+
for (const branch of intersectionNode.types) {
|
|
353
|
+
if (ts.isTypeReferenceNode(branch) || ts.isIndexedAccessTypeNode(branch)) {
|
|
354
|
+
const branchType = checker.getTypeAtLocation(branch);
|
|
355
|
+
const branchProps = branchType.getProperties();
|
|
356
|
+
if (branchProps.length > 0 &&
|
|
357
|
+
branchProps.every((p) => isExternalProperty(p, isExternalFile))) {
|
|
358
|
+
intersectionTypes.push(branch.getText());
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return { properties, intersectionTypes };
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Detect a Svelte 5 reactivity rune from a variable or property initializer.
|
|
367
|
+
*
|
|
368
|
+
* Inspects the AST since runes erase to their inner type after type-checking.
|
|
369
|
+
* Returns `undefined` for any non-rune expression. See the `Reactivity` enum
|
|
370
|
+
* in `types.ts` for the rationale on running this on every file regardless of
|
|
371
|
+
* extension.
|
|
372
|
+
*/
|
|
373
|
+
export const detectReactivity = (initializer) => {
|
|
374
|
+
// Unwrap type-only wrappers so e.g. `$state(0) as Foo` and `($state(0))` are
|
|
375
|
+
// still detected. Runtime semantics are unchanged by these wrappers.
|
|
376
|
+
let expr = initializer;
|
|
377
|
+
while (expr &&
|
|
378
|
+
(ts.isParenthesizedExpression(expr) ||
|
|
379
|
+
ts.isAsExpression(expr) ||
|
|
380
|
+
ts.isSatisfiesExpression(expr) ||
|
|
381
|
+
ts.isNonNullExpression(expr) ||
|
|
382
|
+
ts.isTypeAssertionExpression(expr))) {
|
|
383
|
+
expr = expr.expression;
|
|
384
|
+
}
|
|
385
|
+
if (!expr || !ts.isCallExpression(expr))
|
|
386
|
+
return undefined;
|
|
387
|
+
const callee = expr.expression;
|
|
388
|
+
if (ts.isIdentifier(callee)) {
|
|
389
|
+
if (callee.text === '$state')
|
|
390
|
+
return '$state';
|
|
391
|
+
if (callee.text === '$derived')
|
|
392
|
+
return '$derived';
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.expression)) {
|
|
396
|
+
const base = callee.expression.text;
|
|
397
|
+
const prop = callee.name.text;
|
|
398
|
+
if (base === '$state' && prop === 'raw')
|
|
399
|
+
return '$state.raw';
|
|
400
|
+
if (base === '$derived' && prop === 'by')
|
|
401
|
+
return '$derived.by';
|
|
402
|
+
}
|
|
403
|
+
return undefined;
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* Extract line and column from a TypeScript node.
|
|
407
|
+
* Returns 1-based line and column numbers.
|
|
408
|
+
*/
|
|
409
|
+
export const getNodeLocation = (node) => {
|
|
410
|
+
const sourceFile = node.getSourceFile();
|
|
411
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
412
|
+
return {
|
|
413
|
+
file: sourceFile.fileName,
|
|
414
|
+
line: line + 1, // Convert to 1-based
|
|
415
|
+
column: character + 1, // Convert to 1-based
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
/**
|
|
419
|
+
* Parse a TypeScript generic type parameter declaration into structured info.
|
|
420
|
+
*
|
|
421
|
+
* @param param - the TypeScript type parameter declaration node
|
|
422
|
+
* @returns structured `GenericParamJson` with name, constraint, and default type
|
|
423
|
+
*/
|
|
424
|
+
export const parseGenericParam = (param) => {
|
|
425
|
+
const result = {
|
|
426
|
+
name: param.name.text,
|
|
427
|
+
};
|
|
428
|
+
if (param.constraint) {
|
|
429
|
+
result.constraint = param.constraint.getText();
|
|
430
|
+
}
|
|
431
|
+
if (param.default) {
|
|
432
|
+
result.defaultType = param.default.getText();
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* Extract modifier keywords from a node's modifiers.
|
|
438
|
+
*
|
|
439
|
+
* Returns an array of modifier strings like `['public', 'readonly', 'static']`.
|
|
440
|
+
*/
|
|
441
|
+
export const extractModifiers = (modifiers) => {
|
|
442
|
+
const modifierFlags = [];
|
|
443
|
+
if (!modifiers)
|
|
444
|
+
return modifierFlags;
|
|
445
|
+
for (const mod of modifiers) {
|
|
446
|
+
if (mod.kind === ts.SyntaxKind.PublicKeyword)
|
|
447
|
+
modifierFlags.push('public');
|
|
448
|
+
else if (mod.kind === ts.SyntaxKind.ProtectedKeyword)
|
|
449
|
+
modifierFlags.push('protected');
|
|
450
|
+
else if (mod.kind === ts.SyntaxKind.ReadonlyKeyword)
|
|
451
|
+
modifierFlags.push('readonly');
|
|
452
|
+
else if (mod.kind === ts.SyntaxKind.StaticKeyword)
|
|
453
|
+
modifierFlags.push('static');
|
|
454
|
+
else if (mod.kind === ts.SyntaxKind.AbstractKeyword)
|
|
455
|
+
modifierFlags.push('abstract');
|
|
456
|
+
}
|
|
457
|
+
return modifierFlags;
|
|
458
|
+
};
|
|
459
|
+
/**
|
|
460
|
+
* Append a `(call)` or `(construct)` signature member to a declaration.
|
|
461
|
+
*
|
|
462
|
+
* Captures the extraction logic shared by interface processing
|
|
463
|
+
* (`extractTypeInfo`) and type-alias property processing
|
|
464
|
+
* (`extractTypeAliasProperties`): type signature, parameters, generics,
|
|
465
|
+
* overloads, and TSDoc. The TSDoc source node is supplied by the caller —
|
|
466
|
+
* interfaces look it up in `node.members` (skipping TSDoc when no inline
|
|
467
|
+
* signature is declared, even if one is inherited), type aliases use
|
|
468
|
+
* `sig.getDeclaration()`.
|
|
469
|
+
*
|
|
470
|
+
* @param getSignatures - thunk to retrieve `getCallSignatures()` /
|
|
471
|
+
* `getConstructSignatures()`; called inside the try so checker errors are
|
|
472
|
+
* captured as diagnostics
|
|
473
|
+
* @param signatureKind - `'call'` (member kind: function, includes returnType)
|
|
474
|
+
* or `'construct'` (member kind: constructor, no returnType)
|
|
475
|
+
* @param resolveTsdocNode - callback returning the AST node to parse TSDoc
|
|
476
|
+
* from, or `undefined` to skip TSDoc resolution
|
|
477
|
+
* @param paramValidationFallbackNode - location used by `validateParamKeys`
|
|
478
|
+
* when `resolveTsdocNode` returns `undefined`
|
|
479
|
+
* @param declaration - parent declaration (mutated; appended to `members`)
|
|
480
|
+
* @param errorContext.node - parent node used to locate diagnostics
|
|
481
|
+
* @param errorContext.kindLabel - `'interface'` or `'type'`, included in the
|
|
482
|
+
* diagnostic message
|
|
483
|
+
*
|
|
484
|
+
* @mutates declaration - appends a member when signatures are present;
|
|
485
|
+
* sets `partial: true` on extraction failure
|
|
486
|
+
* @mutates diagnostics - adds `signature_analysis_failed` on checker error
|
|
487
|
+
*/
|
|
488
|
+
export const emitCallOrConstructSignature = (getSignatures, signatureKind, resolveTsdocNode, paramValidationFallbackNode, declaration, checker, diagnostics, errorContext) => {
|
|
489
|
+
try {
|
|
490
|
+
const signatures = getSignatures();
|
|
491
|
+
if (signatures.length === 0)
|
|
492
|
+
return;
|
|
493
|
+
const memberName = signatureKind === 'call' ? '(call)' : '(construct)';
|
|
494
|
+
const memberKind = signatureKind === 'call' ? 'function' : 'constructor';
|
|
495
|
+
const member = { name: memberName, kind: memberKind };
|
|
496
|
+
const sig = signatures[0];
|
|
497
|
+
member.typeSignature = checker.signatureToString(sig);
|
|
498
|
+
if (signatureKind === 'call') {
|
|
499
|
+
member.returnType = checker.typeToString(checker.getReturnTypeOfSignature(sig));
|
|
500
|
+
}
|
|
501
|
+
const tsdocNode = resolveTsdocNode(sig);
|
|
502
|
+
const tsdoc = tsdocNode ? parseComment(tsdocNode, tsdocNode.getSourceFile()) : undefined;
|
|
503
|
+
applyToDeclaration(member, tsdoc);
|
|
504
|
+
member.parameters = extractSignatureParameters(sig, checker, tsdoc?.params);
|
|
505
|
+
validateParamKeys(tsdoc?.params, member.parameters, tsdocNode ?? paramValidationFallbackNode, memberName, diagnostics);
|
|
506
|
+
const sigDecl = sig.getDeclaration();
|
|
507
|
+
if (ts.isFunctionLike(sigDecl) && sigDecl.typeParameters?.length) {
|
|
508
|
+
member.genericParams = sigDecl.typeParameters.map(parseGenericParam);
|
|
509
|
+
}
|
|
510
|
+
if (signatures.length > 1) {
|
|
511
|
+
member.overloads = extractOverloads(signatures, checker, tsdoc, memberName, diagnostics);
|
|
512
|
+
}
|
|
513
|
+
(declaration.members ??= []).push(member);
|
|
514
|
+
}
|
|
515
|
+
catch (err) {
|
|
516
|
+
declaration.partial = true;
|
|
517
|
+
const loc = getNodeLocation(errorContext.node);
|
|
518
|
+
diagnostics.push({
|
|
519
|
+
kind: 'signature_analysis_failed',
|
|
520
|
+
file: loc.file,
|
|
521
|
+
line: loc.line,
|
|
522
|
+
column: loc.column,
|
|
523
|
+
message: `Failed to analyze ${signatureKind} signatures for ${errorContext.kindLabel} "${declaration.name}": ${err instanceof Error ? err.message : String(err)}`,
|
|
524
|
+
severity: 'warning',
|
|
525
|
+
functionName: declaration.name ?? '<default export>',
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property extraction for TypeScript type aliases.
|
|
3
|
+
*
|
|
4
|
+
* `extractTypeAliasProperties` walks the resolved type and emits members for
|
|
5
|
+
* named properties, index signatures, call signatures, and construct
|
|
6
|
+
* signatures. Handles object literals, intersections, mapped types
|
|
7
|
+
* (Partial, Pick, Readonly, etc.), type references, and function types.
|
|
8
|
+
* Called by `extractTypeInfo` in `typescript-extract-type.ts` for type
|
|
9
|
+
* aliases (interfaces use a different path that walks `node.members`
|
|
10
|
+
* directly).
|
|
11
|
+
*
|
|
12
|
+
* @see `typescript-extract-shared.ts` for shared helpers
|
|
13
|
+
* @see `typescript-extract-type.ts` for the dispatcher
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import ts from 'typescript';
|
|
18
|
+
import type { DeclarationJsonBuild } from './declaration-build.js';
|
|
19
|
+
import { type Diagnostic } from './diagnostics.js';
|
|
20
|
+
import { type IsExternalFile } from './typescript-program.js';
|
|
21
|
+
/**
|
|
22
|
+
* Extract properties from a type alias via the TypeScript checker API.
|
|
23
|
+
*
|
|
24
|
+
* Handles object literals, intersections, mapped types (Partial, Pick, Readonly, etc.),
|
|
25
|
+
* type references, and function types. Extracts:
|
|
26
|
+
* - Named properties (with readonly/optional detection, TSDoc from declarations)
|
|
27
|
+
* - Index signatures (string/number)
|
|
28
|
+
* - Call signatures (`(call)`)
|
|
29
|
+
* - Construct signatures (`(construct)`)
|
|
30
|
+
*
|
|
31
|
+
* @mutates declaration - adds members
|
|
32
|
+
*/
|
|
33
|
+
export declare const extractTypeAliasProperties: (node: ts.TypeAliasDeclaration, nodeType: ts.Type, checker: ts.TypeChecker, declaration: DeclarationJsonBuild, diagnostics: Array<Diagnostic>, isExternalFile: IsExternalFile) => void;
|
|
34
|
+
//# sourceMappingURL=typescript-extract-type-properties.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typescript-extract-type-properties.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/typescript-extract-type-properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,OAAO,KAAK,EAAC,oBAAoB,EAAkB,MAAM,wBAAwB,CAAC;AAClF,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAC,KAAK,cAAc,EAAC,MAAM,yBAAyB,CAAC;AA+J5D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,GACtC,MAAM,EAAE,CAAC,oBAAoB,EAC7B,UAAU,EAAE,CAAC,IAAI,EACjB,SAAS,EAAE,CAAC,WAAW,EACvB,aAAa,oBAAoB,EACjC,aAAa,KAAK,CAAC,UAAU,CAAC,EAC9B,gBAAgB,cAAc,KAC5B,IA2IF,CAAC"}
|