uidex 0.6.0 → 0.7.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 +3 -3
- package/dist/cli/cli.cjs +1510 -1244
- package/dist/cli/cli.cjs.map +1 -1
- package/dist/cloud/index.cjs +385 -175
- package/dist/cloud/index.cjs.map +1 -1
- package/dist/cloud/index.d.cts +192 -4
- package/dist/cloud/index.d.ts +192 -4
- package/dist/cloud/index.js +377 -177
- package/dist/cloud/index.js.map +1 -1
- package/dist/headless/index.cjs +82 -255
- package/dist/headless/index.cjs.map +1 -1
- package/dist/headless/index.d.cts +5 -11
- package/dist/headless/index.d.ts +5 -11
- package/dist/headless/index.js +82 -257
- package/dist/headless/index.js.map +1 -1
- package/dist/index.cjs +721 -1053
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +149 -160
- package/dist/index.d.ts +149 -160
- package/dist/index.js +741 -1068
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +729 -1000
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +99 -86
- package/dist/react/index.d.ts +99 -86
- package/dist/react/index.js +745 -1015
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +1518 -1237
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +209 -12
- package/dist/scan/index.d.ts +209 -12
- package/dist/scan/index.js +1515 -1236
- package/dist/scan/index.js.map +1 -1
- package/package.json +22 -21
- package/templates/claude/SKILL.md +71 -0
- package/templates/claude/references/audit.md +43 -0
- package/templates/claude/{rules.md → references/conventions.md} +25 -28
- package/templates/claude/audit.md +0 -43
- /package/templates/claude/{api.md → references/api.md} +0 -0
package/dist/scan/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Program } from 'oxc-parser';
|
|
2
|
+
|
|
1
3
|
declare const ENTITY_KINDS: readonly ["route", "page", "feature", "widget", "region", "element", "primitive", "flow"];
|
|
2
4
|
type EntityKind = (typeof ENTITY_KINDS)[number];
|
|
3
5
|
type Scope = string;
|
|
@@ -96,7 +98,7 @@ interface Registry {
|
|
|
96
98
|
setReports(kind: EntityKind, id: string, reports: readonly ReportRecord[]): void;
|
|
97
99
|
getReports(kind: EntityKind, id: string): readonly ReportRecord[];
|
|
98
100
|
listReportKeys(): readonly string[];
|
|
99
|
-
|
|
101
|
+
closeReport?: (reportId: string, status?: string) => void | Promise<void>;
|
|
100
102
|
onReportsChange(cb: () => void): () => void;
|
|
101
103
|
}
|
|
102
104
|
|
|
@@ -118,14 +120,12 @@ interface AuditConfig {
|
|
|
118
120
|
coverage?: boolean;
|
|
119
121
|
acceptance?: boolean;
|
|
120
122
|
}
|
|
121
|
-
type TypeMode = "strict" | "loose";
|
|
122
123
|
interface UidexConfig {
|
|
123
124
|
$schema?: string;
|
|
124
125
|
sources: SourceConfig[];
|
|
125
126
|
exclude?: string[];
|
|
126
127
|
output: string;
|
|
127
128
|
flows?: string[];
|
|
128
|
-
typeMode?: TypeMode;
|
|
129
129
|
audit?: AuditConfig;
|
|
130
130
|
conventions?: ConventionsConfig;
|
|
131
131
|
}
|
|
@@ -140,7 +140,12 @@ interface ScannedFile {
|
|
|
140
140
|
displayPath: string;
|
|
141
141
|
content: string;
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
/** Byte-offset range into a ScannedFile's content (UTF-16 code units, like oxc spans). */
|
|
144
|
+
interface Span {
|
|
145
|
+
start: number;
|
|
146
|
+
end: number;
|
|
147
|
+
}
|
|
148
|
+
type AnnotationKind = "element" | "region" | "widget" | "primitive";
|
|
144
149
|
type DomAttrKind = "element" | "region" | "widget" | "primitive";
|
|
145
150
|
interface AnnotationAncestor {
|
|
146
151
|
kind: DomAttrKind;
|
|
@@ -151,10 +156,10 @@ interface Annotation {
|
|
|
151
156
|
id: string;
|
|
152
157
|
file: string;
|
|
153
158
|
line: number;
|
|
154
|
-
|
|
155
|
-
acceptance?: string[];
|
|
156
|
-
/** JSX ancestor chain, outermost to innermost. Only populated for DOM-attribute kinds. */
|
|
159
|
+
/** JSX ancestor chain, outermost to innermost. */
|
|
157
160
|
ancestors?: AnnotationAncestor[];
|
|
161
|
+
/** The span of the attribute's value literal. */
|
|
162
|
+
span?: Span;
|
|
158
163
|
}
|
|
159
164
|
type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "region" | "flow";
|
|
160
165
|
interface MetadataExport {
|
|
@@ -168,12 +173,86 @@ interface MetadataExport {
|
|
|
168
173
|
widgets?: string[];
|
|
169
174
|
notFlow?: boolean;
|
|
170
175
|
loc: Location;
|
|
176
|
+
/** Span of the whole `export const uidex = ...` statement (incl. trailing `;`). */
|
|
177
|
+
span?: Span;
|
|
178
|
+
/** Span of the id string literal (the discriminator field's value). */
|
|
179
|
+
idSpan?: Span;
|
|
180
|
+
/**
|
|
181
|
+
* Removal-ready spans of top-level fields (key through value, extended over
|
|
182
|
+
* one adjacent comma) keyed by field name. Used by `--fix` codemods.
|
|
183
|
+
*/
|
|
184
|
+
fieldSpans?: Record<string, Span>;
|
|
185
|
+
/** Spans of the string-literal items in `features`, parallel to `features`. */
|
|
186
|
+
featureSpans?: Span[];
|
|
187
|
+
/** Spans of the string-literal items in `widgets`, parallel to `widgets`. */
|
|
188
|
+
widgetSpans?: Span[];
|
|
189
|
+
}
|
|
190
|
+
/** A data-uidex* attribute whose value the scanner could not resolve. */
|
|
191
|
+
interface DynamicAttrFact {
|
|
192
|
+
kind: DomAttrKind;
|
|
193
|
+
attrName: string;
|
|
194
|
+
line: number;
|
|
195
|
+
}
|
|
196
|
+
/** An HTML5 landmark element (`<header>`, `<nav>`, …, or `role="region"`). */
|
|
197
|
+
interface LandmarkFact {
|
|
198
|
+
/** the host tag, or "region" for role="region" */
|
|
199
|
+
tag: string;
|
|
200
|
+
line: number;
|
|
201
|
+
}
|
|
202
|
+
/** A static import binding (import / export-from), for the scope-leak check. */
|
|
203
|
+
interface ImportFact {
|
|
204
|
+
specifier: string;
|
|
205
|
+
line: number;
|
|
206
|
+
/** span of the whole import/export statement */
|
|
207
|
+
span: Span;
|
|
208
|
+
/** `import type` or an export-from with type-only kind */
|
|
209
|
+
isTypeOnly: boolean;
|
|
210
|
+
/** imported local binding names (default + named + namespace) */
|
|
211
|
+
names: string[];
|
|
212
|
+
}
|
|
213
|
+
/** An interactive host element (`button`, `a`, …) with no data-uidex* attribute. */
|
|
214
|
+
interface InteractiveElementFact {
|
|
215
|
+
tag: string;
|
|
216
|
+
line: number;
|
|
217
|
+
/** the element carries a `{...props}` spread that may deliver the annotation */
|
|
218
|
+
hasSpread: boolean;
|
|
219
|
+
/** offset just after the tag name; where `--fix` inserts the generated attribute */
|
|
220
|
+
nameEnd: number;
|
|
221
|
+
/**
|
|
222
|
+
* Best static label for deriving an id (aria-label, visible text, title,
|
|
223
|
+
* name, placeholder). Absent when nothing usable is statically available.
|
|
224
|
+
*/
|
|
225
|
+
nameHint?: string;
|
|
226
|
+
}
|
|
227
|
+
interface FlowCallFact {
|
|
228
|
+
id: string;
|
|
229
|
+
action?: string;
|
|
230
|
+
line: number;
|
|
231
|
+
/** span of the id string literal argument */
|
|
232
|
+
span?: Span;
|
|
233
|
+
}
|
|
234
|
+
/** A `uidex(expr)` call whose argument is not a static string literal. */
|
|
235
|
+
interface DynamicFlowCallFact {
|
|
236
|
+
line: number;
|
|
237
|
+
}
|
|
238
|
+
/** A tagged `test.describe(..., { tag: "@uidex:flow" }, ...)` occurrence. */
|
|
239
|
+
interface FlowFact {
|
|
240
|
+
title: string;
|
|
241
|
+
line: number;
|
|
242
|
+
calls: FlowCallFact[];
|
|
243
|
+
/** uidex() calls inside this describe whose ids could not be resolved */
|
|
244
|
+
dynamicCalls?: DynamicFlowCallFact[];
|
|
171
245
|
}
|
|
172
246
|
interface ExtractedFile {
|
|
173
247
|
file: ScannedFile;
|
|
174
248
|
annotations: Annotation[];
|
|
175
249
|
metadata?: MetadataExport[];
|
|
176
250
|
diagnostics?: Diagnostic[];
|
|
251
|
+
flows?: FlowFact[];
|
|
252
|
+
dynamicAttrs?: DynamicAttrFact[];
|
|
253
|
+
unannotatedInteractive?: InteractiveElementFact[];
|
|
254
|
+
landmarks?: LandmarkFact[];
|
|
255
|
+
imports?: ImportFact[];
|
|
177
256
|
}
|
|
178
257
|
interface DetectedRoute {
|
|
179
258
|
id: string;
|
|
@@ -181,6 +260,30 @@ interface DetectedRoute {
|
|
|
181
260
|
file: string;
|
|
182
261
|
}
|
|
183
262
|
type DiagnosticSeverity = "error" | "warning" | "info";
|
|
263
|
+
/** A single text replacement; offsets index the file's content at scan time. */
|
|
264
|
+
interface FixEdit {
|
|
265
|
+
/** absolute path of the file to edit */
|
|
266
|
+
path: string;
|
|
267
|
+
start: number;
|
|
268
|
+
end: number;
|
|
269
|
+
replacement: string;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A machine-applicable fix attached to a diagnostic. Applied by
|
|
273
|
+
* `uidex scan --fix`; edits are computed against the exact content the
|
|
274
|
+
* scanner read, so they must be applied before any other modification.
|
|
275
|
+
*/
|
|
276
|
+
interface DiagnosticFix {
|
|
277
|
+
description: string;
|
|
278
|
+
edits?: FixEdit[];
|
|
279
|
+
/** files to create (absolute path → content); skipped if the file exists */
|
|
280
|
+
createFiles?: Array<{
|
|
281
|
+
path: string;
|
|
282
|
+
content: string;
|
|
283
|
+
}>;
|
|
284
|
+
/** absolute paths of files to delete */
|
|
285
|
+
deleteFiles?: string[];
|
|
286
|
+
}
|
|
184
287
|
interface Diagnostic {
|
|
185
288
|
code: string;
|
|
186
289
|
severity: DiagnosticSeverity;
|
|
@@ -192,6 +295,7 @@ interface Diagnostic {
|
|
|
192
295
|
id: string;
|
|
193
296
|
};
|
|
194
297
|
hint?: string;
|
|
298
|
+
fix?: DiagnosticFix;
|
|
195
299
|
}
|
|
196
300
|
interface AuditSummary {
|
|
197
301
|
diagnostics: Diagnostic[];
|
|
@@ -231,11 +335,36 @@ declare function walk(sources: SourceConfig[], options: WalkOptions): ScannedFil
|
|
|
231
335
|
|
|
232
336
|
declare function extract(files: ScannedFile[]): ExtractedFile[];
|
|
233
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Shared parse layer for the scanner. Each file is parsed exactly once in
|
|
340
|
+
* the extract phase; the resulting AST never leaves it (ExtractedFile must
|
|
341
|
+
* stay JSON-serializable for the bundler-plugin watchers).
|
|
342
|
+
*
|
|
343
|
+
* oxc spans are UTF-16 code-unit offsets, i.e. they index directly into the
|
|
344
|
+
* JS source string (verified against oxc-parser@0.135.0).
|
|
345
|
+
*/
|
|
346
|
+
interface ParsedComment {
|
|
347
|
+
type: "Line" | "Block";
|
|
348
|
+
value: string;
|
|
349
|
+
start: number;
|
|
350
|
+
end: number;
|
|
351
|
+
}
|
|
352
|
+
interface ParsedSource {
|
|
353
|
+
/** null when parsing failed catastrophically — callers degrade to empty facts */
|
|
354
|
+
program: Program | null;
|
|
355
|
+
/** true when oxc reported recoverable errors (program is still usable) */
|
|
356
|
+
hasErrors: boolean;
|
|
357
|
+
/** all comments in source order; empty when parsing failed */
|
|
358
|
+
comments: ParsedComment[];
|
|
359
|
+
/** offset → 1-based line */
|
|
360
|
+
lineAt(offset: number): number;
|
|
361
|
+
}
|
|
362
|
+
|
|
234
363
|
interface UidexExportExtractResult {
|
|
235
364
|
exports: MetadataExport[];
|
|
236
365
|
diagnostics: Diagnostic[];
|
|
237
366
|
}
|
|
238
|
-
declare function extractUidexExports(file: ScannedFile): UidexExportExtractResult;
|
|
367
|
+
declare function extractUidexExports(file: ScannedFile, parsed?: ParsedSource): UidexExportExtractResult;
|
|
239
368
|
|
|
240
369
|
interface ResolveContext {
|
|
241
370
|
config: UidexConfig;
|
|
@@ -258,6 +387,8 @@ interface AuditOptions {
|
|
|
258
387
|
registry: Registry;
|
|
259
388
|
extracted: ExtractedFile[];
|
|
260
389
|
files: ScannedFile[];
|
|
390
|
+
/** Extract output for flow spec files; enables per-call reference checks. */
|
|
391
|
+
flowExtracted?: ExtractedFile[];
|
|
261
392
|
config: UidexConfig;
|
|
262
393
|
check?: boolean;
|
|
263
394
|
lint?: boolean;
|
|
@@ -276,8 +407,6 @@ interface EmitOptions {
|
|
|
276
407
|
gitContext?: GitContext;
|
|
277
408
|
/** The import source for `createUidex` in the generated preconfigured export. */
|
|
278
409
|
uidexImport?: string;
|
|
279
|
-
/** Controls id-union emission: "strict" emits literal unions, "loose" emits `string`. */
|
|
280
|
-
typeMode?: TypeMode;
|
|
281
410
|
}
|
|
282
411
|
declare function emit(opts: EmitOptions): string;
|
|
283
412
|
|
|
@@ -297,6 +426,15 @@ interface GitResolveOptions {
|
|
|
297
426
|
}
|
|
298
427
|
declare function resolveGitContext(opts?: GitResolveOptions): GitContext;
|
|
299
428
|
|
|
429
|
+
type ScaffoldKind = "widget" | "page" | "feature";
|
|
430
|
+
interface ScaffoldSpecOptions {
|
|
431
|
+
registry: Registry;
|
|
432
|
+
kind: ScaffoldKind;
|
|
433
|
+
id: string;
|
|
434
|
+
outDir: string;
|
|
435
|
+
force?: boolean;
|
|
436
|
+
fixtureImport?: string;
|
|
437
|
+
}
|
|
300
438
|
interface ScaffoldOptions {
|
|
301
439
|
registry: Registry;
|
|
302
440
|
widgetId: string;
|
|
@@ -311,6 +449,13 @@ interface ScaffoldResult {
|
|
|
311
449
|
reason?: string;
|
|
312
450
|
}
|
|
313
451
|
declare function scaffoldWidgetSpec(opts: ScaffoldOptions): ScaffoldResult;
|
|
452
|
+
/**
|
|
453
|
+
* Emits a tagged Playwright stub from an entity's declared acceptance
|
|
454
|
+
* criteria — one `test()` per criterion. Widgets keep the historical
|
|
455
|
+
* `widget-<id>.spec.ts` name; pages and features emit `flow-<id>.spec.ts`
|
|
456
|
+
* per the one-tagged-describe-per-flow-spec convention.
|
|
457
|
+
*/
|
|
458
|
+
declare function scaffoldSpec(opts: ScaffoldSpecOptions): ScaffoldResult;
|
|
314
459
|
|
|
315
460
|
interface RunScanOptions {
|
|
316
461
|
cwd?: string;
|
|
@@ -330,6 +475,59 @@ interface ScanResult {
|
|
|
330
475
|
declare function runScan(opts?: RunScanOptions): ScanResult[];
|
|
331
476
|
declare function writeScanResult(result: ScanResult): void;
|
|
332
477
|
|
|
478
|
+
/**
|
|
479
|
+
* Applies the machine-generated fixes attached to diagnostics.
|
|
480
|
+
*
|
|
481
|
+
* Edits are offset-based against the content the scanner read, so this must
|
|
482
|
+
* run before anything else touches the files. Within a file, edits apply
|
|
483
|
+
* last-to-first; an edit overlapping one already applied is skipped. Identical
|
|
484
|
+
* edits are deduped — two fixes in one file emitting the same edit collapse to
|
|
485
|
+
* one application.
|
|
486
|
+
*/
|
|
487
|
+
interface AppliedFix {
|
|
488
|
+
code: string;
|
|
489
|
+
description: string;
|
|
490
|
+
file?: string;
|
|
491
|
+
}
|
|
492
|
+
interface ApplyFixesResult {
|
|
493
|
+
applied: AppliedFix[];
|
|
494
|
+
/** fixes skipped because of overlapping edits or existing target files */
|
|
495
|
+
skipped: Array<AppliedFix & {
|
|
496
|
+
reason: string;
|
|
497
|
+
}>;
|
|
498
|
+
}
|
|
499
|
+
declare function applyFixes(diagnostics: Diagnostic[]): ApplyFixesResult;
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Cross-file id rename. The registry knows every reference surface for an
|
|
503
|
+
* id — the DOM attribute, `uidex("…")` calls in flow specs, the widget
|
|
504
|
+
* export's discriminator, and `widgets:` arrays — so the rename rewrites all
|
|
505
|
+
* of them in one pass and regenerates the gen file. Occurrences the scanner
|
|
506
|
+
* can't edit mechanically (const indirection, ternaries, patterns) are
|
|
507
|
+
* reported for manual follow-up instead of being guessed at.
|
|
508
|
+
*/
|
|
509
|
+
type RenameKind = "element" | "widget" | "region";
|
|
510
|
+
interface RenameOptions {
|
|
511
|
+
cwd: string;
|
|
512
|
+
kind: RenameKind;
|
|
513
|
+
oldId: string;
|
|
514
|
+
newId: string;
|
|
515
|
+
/** allow renaming onto an id that already exists */
|
|
516
|
+
force?: boolean;
|
|
517
|
+
}
|
|
518
|
+
interface RenameResult {
|
|
519
|
+
/** number of source edits applied */
|
|
520
|
+
edits: number;
|
|
521
|
+
/** occurrences that need a human (no static literal to rewrite) */
|
|
522
|
+
manual: Array<{
|
|
523
|
+
file: string;
|
|
524
|
+
line: number;
|
|
525
|
+
reason: string;
|
|
526
|
+
}>;
|
|
527
|
+
errors: string[];
|
|
528
|
+
}
|
|
529
|
+
declare function renameEntity(opts: RenameOptions): RenameResult;
|
|
530
|
+
|
|
333
531
|
interface CliResult {
|
|
334
532
|
exitCode: number;
|
|
335
533
|
stdout: string;
|
|
@@ -341,7 +539,6 @@ interface CliOptions {
|
|
|
341
539
|
}
|
|
342
540
|
declare function run(opts: CliOptions): Promise<CliResult>;
|
|
343
541
|
|
|
344
|
-
declare const DEFAULT_TYPE_MODE: TypeMode;
|
|
345
542
|
declare class ConfigError extends Error {
|
|
346
543
|
constructor(message: string);
|
|
347
544
|
}
|
|
@@ -402,4 +599,4 @@ declare namespace Uidex {
|
|
|
402
599
|
}
|
|
403
600
|
}
|
|
404
601
|
|
|
405
|
-
export { type Annotation, type AnnotationKind, type AuditConfig, type AuditSummary, CONFIG_FILENAME, type CliOptions, type CliResult, ConfigError, type ConventionsConfig, DEFAULT_CONVENTIONS,
|
|
602
|
+
export { type Annotation, type AnnotationKind, type AppliedFix, type ApplyFixesResult, type AuditConfig, type AuditSummary, CONFIG_FILENAME, type CliOptions, type CliResult, ConfigError, type ConventionsConfig, DEFAULT_CONVENTIONS, type DetectedRoute, type Diagnostic, type DiagnosticFix, type DiagnosticSeverity, type DiscoveredConfig, type ExtractedFile, type FixEdit, type GitContext, type ImportFact, type LandmarkFact, type MetadataExport, type MetadataExportKind, type RenameKind, type RenameOptions, type RenameResult, type RunScanOptions, type ScaffoldKind, type ScaffoldOptions, type ScaffoldResult, type ScaffoldSpecOptions, type ScanResult, type ScannedFile, type SourceConfig, type Span, Uidex, type UidexConfig, applyFixes, audit, detectRoutes, discover, emit, extract, extractUidexExports, globToRegExp, parseConfig, pathToId, renameEntity, resolve, resolveGitContext, run as runCli, runScan, scaffoldSpec, scaffoldWidgetSpec, validateConfig, walk, writeScanResult };
|
package/dist/scan/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Program } from 'oxc-parser';
|
|
2
|
+
|
|
1
3
|
declare const ENTITY_KINDS: readonly ["route", "page", "feature", "widget", "region", "element", "primitive", "flow"];
|
|
2
4
|
type EntityKind = (typeof ENTITY_KINDS)[number];
|
|
3
5
|
type Scope = string;
|
|
@@ -96,7 +98,7 @@ interface Registry {
|
|
|
96
98
|
setReports(kind: EntityKind, id: string, reports: readonly ReportRecord[]): void;
|
|
97
99
|
getReports(kind: EntityKind, id: string): readonly ReportRecord[];
|
|
98
100
|
listReportKeys(): readonly string[];
|
|
99
|
-
|
|
101
|
+
closeReport?: (reportId: string, status?: string) => void | Promise<void>;
|
|
100
102
|
onReportsChange(cb: () => void): () => void;
|
|
101
103
|
}
|
|
102
104
|
|
|
@@ -118,14 +120,12 @@ interface AuditConfig {
|
|
|
118
120
|
coverage?: boolean;
|
|
119
121
|
acceptance?: boolean;
|
|
120
122
|
}
|
|
121
|
-
type TypeMode = "strict" | "loose";
|
|
122
123
|
interface UidexConfig {
|
|
123
124
|
$schema?: string;
|
|
124
125
|
sources: SourceConfig[];
|
|
125
126
|
exclude?: string[];
|
|
126
127
|
output: string;
|
|
127
128
|
flows?: string[];
|
|
128
|
-
typeMode?: TypeMode;
|
|
129
129
|
audit?: AuditConfig;
|
|
130
130
|
conventions?: ConventionsConfig;
|
|
131
131
|
}
|
|
@@ -140,7 +140,12 @@ interface ScannedFile {
|
|
|
140
140
|
displayPath: string;
|
|
141
141
|
content: string;
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
/** Byte-offset range into a ScannedFile's content (UTF-16 code units, like oxc spans). */
|
|
144
|
+
interface Span {
|
|
145
|
+
start: number;
|
|
146
|
+
end: number;
|
|
147
|
+
}
|
|
148
|
+
type AnnotationKind = "element" | "region" | "widget" | "primitive";
|
|
144
149
|
type DomAttrKind = "element" | "region" | "widget" | "primitive";
|
|
145
150
|
interface AnnotationAncestor {
|
|
146
151
|
kind: DomAttrKind;
|
|
@@ -151,10 +156,10 @@ interface Annotation {
|
|
|
151
156
|
id: string;
|
|
152
157
|
file: string;
|
|
153
158
|
line: number;
|
|
154
|
-
|
|
155
|
-
acceptance?: string[];
|
|
156
|
-
/** JSX ancestor chain, outermost to innermost. Only populated for DOM-attribute kinds. */
|
|
159
|
+
/** JSX ancestor chain, outermost to innermost. */
|
|
157
160
|
ancestors?: AnnotationAncestor[];
|
|
161
|
+
/** The span of the attribute's value literal. */
|
|
162
|
+
span?: Span;
|
|
158
163
|
}
|
|
159
164
|
type MetadataExportKind = "page" | "feature" | "primitive" | "widget" | "region" | "flow";
|
|
160
165
|
interface MetadataExport {
|
|
@@ -168,12 +173,86 @@ interface MetadataExport {
|
|
|
168
173
|
widgets?: string[];
|
|
169
174
|
notFlow?: boolean;
|
|
170
175
|
loc: Location;
|
|
176
|
+
/** Span of the whole `export const uidex = ...` statement (incl. trailing `;`). */
|
|
177
|
+
span?: Span;
|
|
178
|
+
/** Span of the id string literal (the discriminator field's value). */
|
|
179
|
+
idSpan?: Span;
|
|
180
|
+
/**
|
|
181
|
+
* Removal-ready spans of top-level fields (key through value, extended over
|
|
182
|
+
* one adjacent comma) keyed by field name. Used by `--fix` codemods.
|
|
183
|
+
*/
|
|
184
|
+
fieldSpans?: Record<string, Span>;
|
|
185
|
+
/** Spans of the string-literal items in `features`, parallel to `features`. */
|
|
186
|
+
featureSpans?: Span[];
|
|
187
|
+
/** Spans of the string-literal items in `widgets`, parallel to `widgets`. */
|
|
188
|
+
widgetSpans?: Span[];
|
|
189
|
+
}
|
|
190
|
+
/** A data-uidex* attribute whose value the scanner could not resolve. */
|
|
191
|
+
interface DynamicAttrFact {
|
|
192
|
+
kind: DomAttrKind;
|
|
193
|
+
attrName: string;
|
|
194
|
+
line: number;
|
|
195
|
+
}
|
|
196
|
+
/** An HTML5 landmark element (`<header>`, `<nav>`, …, or `role="region"`). */
|
|
197
|
+
interface LandmarkFact {
|
|
198
|
+
/** the host tag, or "region" for role="region" */
|
|
199
|
+
tag: string;
|
|
200
|
+
line: number;
|
|
201
|
+
}
|
|
202
|
+
/** A static import binding (import / export-from), for the scope-leak check. */
|
|
203
|
+
interface ImportFact {
|
|
204
|
+
specifier: string;
|
|
205
|
+
line: number;
|
|
206
|
+
/** span of the whole import/export statement */
|
|
207
|
+
span: Span;
|
|
208
|
+
/** `import type` or an export-from with type-only kind */
|
|
209
|
+
isTypeOnly: boolean;
|
|
210
|
+
/** imported local binding names (default + named + namespace) */
|
|
211
|
+
names: string[];
|
|
212
|
+
}
|
|
213
|
+
/** An interactive host element (`button`, `a`, …) with no data-uidex* attribute. */
|
|
214
|
+
interface InteractiveElementFact {
|
|
215
|
+
tag: string;
|
|
216
|
+
line: number;
|
|
217
|
+
/** the element carries a `{...props}` spread that may deliver the annotation */
|
|
218
|
+
hasSpread: boolean;
|
|
219
|
+
/** offset just after the tag name; where `--fix` inserts the generated attribute */
|
|
220
|
+
nameEnd: number;
|
|
221
|
+
/**
|
|
222
|
+
* Best static label for deriving an id (aria-label, visible text, title,
|
|
223
|
+
* name, placeholder). Absent when nothing usable is statically available.
|
|
224
|
+
*/
|
|
225
|
+
nameHint?: string;
|
|
226
|
+
}
|
|
227
|
+
interface FlowCallFact {
|
|
228
|
+
id: string;
|
|
229
|
+
action?: string;
|
|
230
|
+
line: number;
|
|
231
|
+
/** span of the id string literal argument */
|
|
232
|
+
span?: Span;
|
|
233
|
+
}
|
|
234
|
+
/** A `uidex(expr)` call whose argument is not a static string literal. */
|
|
235
|
+
interface DynamicFlowCallFact {
|
|
236
|
+
line: number;
|
|
237
|
+
}
|
|
238
|
+
/** A tagged `test.describe(..., { tag: "@uidex:flow" }, ...)` occurrence. */
|
|
239
|
+
interface FlowFact {
|
|
240
|
+
title: string;
|
|
241
|
+
line: number;
|
|
242
|
+
calls: FlowCallFact[];
|
|
243
|
+
/** uidex() calls inside this describe whose ids could not be resolved */
|
|
244
|
+
dynamicCalls?: DynamicFlowCallFact[];
|
|
171
245
|
}
|
|
172
246
|
interface ExtractedFile {
|
|
173
247
|
file: ScannedFile;
|
|
174
248
|
annotations: Annotation[];
|
|
175
249
|
metadata?: MetadataExport[];
|
|
176
250
|
diagnostics?: Diagnostic[];
|
|
251
|
+
flows?: FlowFact[];
|
|
252
|
+
dynamicAttrs?: DynamicAttrFact[];
|
|
253
|
+
unannotatedInteractive?: InteractiveElementFact[];
|
|
254
|
+
landmarks?: LandmarkFact[];
|
|
255
|
+
imports?: ImportFact[];
|
|
177
256
|
}
|
|
178
257
|
interface DetectedRoute {
|
|
179
258
|
id: string;
|
|
@@ -181,6 +260,30 @@ interface DetectedRoute {
|
|
|
181
260
|
file: string;
|
|
182
261
|
}
|
|
183
262
|
type DiagnosticSeverity = "error" | "warning" | "info";
|
|
263
|
+
/** A single text replacement; offsets index the file's content at scan time. */
|
|
264
|
+
interface FixEdit {
|
|
265
|
+
/** absolute path of the file to edit */
|
|
266
|
+
path: string;
|
|
267
|
+
start: number;
|
|
268
|
+
end: number;
|
|
269
|
+
replacement: string;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A machine-applicable fix attached to a diagnostic. Applied by
|
|
273
|
+
* `uidex scan --fix`; edits are computed against the exact content the
|
|
274
|
+
* scanner read, so they must be applied before any other modification.
|
|
275
|
+
*/
|
|
276
|
+
interface DiagnosticFix {
|
|
277
|
+
description: string;
|
|
278
|
+
edits?: FixEdit[];
|
|
279
|
+
/** files to create (absolute path → content); skipped if the file exists */
|
|
280
|
+
createFiles?: Array<{
|
|
281
|
+
path: string;
|
|
282
|
+
content: string;
|
|
283
|
+
}>;
|
|
284
|
+
/** absolute paths of files to delete */
|
|
285
|
+
deleteFiles?: string[];
|
|
286
|
+
}
|
|
184
287
|
interface Diagnostic {
|
|
185
288
|
code: string;
|
|
186
289
|
severity: DiagnosticSeverity;
|
|
@@ -192,6 +295,7 @@ interface Diagnostic {
|
|
|
192
295
|
id: string;
|
|
193
296
|
};
|
|
194
297
|
hint?: string;
|
|
298
|
+
fix?: DiagnosticFix;
|
|
195
299
|
}
|
|
196
300
|
interface AuditSummary {
|
|
197
301
|
diagnostics: Diagnostic[];
|
|
@@ -231,11 +335,36 @@ declare function walk(sources: SourceConfig[], options: WalkOptions): ScannedFil
|
|
|
231
335
|
|
|
232
336
|
declare function extract(files: ScannedFile[]): ExtractedFile[];
|
|
233
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Shared parse layer for the scanner. Each file is parsed exactly once in
|
|
340
|
+
* the extract phase; the resulting AST never leaves it (ExtractedFile must
|
|
341
|
+
* stay JSON-serializable for the bundler-plugin watchers).
|
|
342
|
+
*
|
|
343
|
+
* oxc spans are UTF-16 code-unit offsets, i.e. they index directly into the
|
|
344
|
+
* JS source string (verified against oxc-parser@0.135.0).
|
|
345
|
+
*/
|
|
346
|
+
interface ParsedComment {
|
|
347
|
+
type: "Line" | "Block";
|
|
348
|
+
value: string;
|
|
349
|
+
start: number;
|
|
350
|
+
end: number;
|
|
351
|
+
}
|
|
352
|
+
interface ParsedSource {
|
|
353
|
+
/** null when parsing failed catastrophically — callers degrade to empty facts */
|
|
354
|
+
program: Program | null;
|
|
355
|
+
/** true when oxc reported recoverable errors (program is still usable) */
|
|
356
|
+
hasErrors: boolean;
|
|
357
|
+
/** all comments in source order; empty when parsing failed */
|
|
358
|
+
comments: ParsedComment[];
|
|
359
|
+
/** offset → 1-based line */
|
|
360
|
+
lineAt(offset: number): number;
|
|
361
|
+
}
|
|
362
|
+
|
|
234
363
|
interface UidexExportExtractResult {
|
|
235
364
|
exports: MetadataExport[];
|
|
236
365
|
diagnostics: Diagnostic[];
|
|
237
366
|
}
|
|
238
|
-
declare function extractUidexExports(file: ScannedFile): UidexExportExtractResult;
|
|
367
|
+
declare function extractUidexExports(file: ScannedFile, parsed?: ParsedSource): UidexExportExtractResult;
|
|
239
368
|
|
|
240
369
|
interface ResolveContext {
|
|
241
370
|
config: UidexConfig;
|
|
@@ -258,6 +387,8 @@ interface AuditOptions {
|
|
|
258
387
|
registry: Registry;
|
|
259
388
|
extracted: ExtractedFile[];
|
|
260
389
|
files: ScannedFile[];
|
|
390
|
+
/** Extract output for flow spec files; enables per-call reference checks. */
|
|
391
|
+
flowExtracted?: ExtractedFile[];
|
|
261
392
|
config: UidexConfig;
|
|
262
393
|
check?: boolean;
|
|
263
394
|
lint?: boolean;
|
|
@@ -276,8 +407,6 @@ interface EmitOptions {
|
|
|
276
407
|
gitContext?: GitContext;
|
|
277
408
|
/** The import source for `createUidex` in the generated preconfigured export. */
|
|
278
409
|
uidexImport?: string;
|
|
279
|
-
/** Controls id-union emission: "strict" emits literal unions, "loose" emits `string`. */
|
|
280
|
-
typeMode?: TypeMode;
|
|
281
410
|
}
|
|
282
411
|
declare function emit(opts: EmitOptions): string;
|
|
283
412
|
|
|
@@ -297,6 +426,15 @@ interface GitResolveOptions {
|
|
|
297
426
|
}
|
|
298
427
|
declare function resolveGitContext(opts?: GitResolveOptions): GitContext;
|
|
299
428
|
|
|
429
|
+
type ScaffoldKind = "widget" | "page" | "feature";
|
|
430
|
+
interface ScaffoldSpecOptions {
|
|
431
|
+
registry: Registry;
|
|
432
|
+
kind: ScaffoldKind;
|
|
433
|
+
id: string;
|
|
434
|
+
outDir: string;
|
|
435
|
+
force?: boolean;
|
|
436
|
+
fixtureImport?: string;
|
|
437
|
+
}
|
|
300
438
|
interface ScaffoldOptions {
|
|
301
439
|
registry: Registry;
|
|
302
440
|
widgetId: string;
|
|
@@ -311,6 +449,13 @@ interface ScaffoldResult {
|
|
|
311
449
|
reason?: string;
|
|
312
450
|
}
|
|
313
451
|
declare function scaffoldWidgetSpec(opts: ScaffoldOptions): ScaffoldResult;
|
|
452
|
+
/**
|
|
453
|
+
* Emits a tagged Playwright stub from an entity's declared acceptance
|
|
454
|
+
* criteria — one `test()` per criterion. Widgets keep the historical
|
|
455
|
+
* `widget-<id>.spec.ts` name; pages and features emit `flow-<id>.spec.ts`
|
|
456
|
+
* per the one-tagged-describe-per-flow-spec convention.
|
|
457
|
+
*/
|
|
458
|
+
declare function scaffoldSpec(opts: ScaffoldSpecOptions): ScaffoldResult;
|
|
314
459
|
|
|
315
460
|
interface RunScanOptions {
|
|
316
461
|
cwd?: string;
|
|
@@ -330,6 +475,59 @@ interface ScanResult {
|
|
|
330
475
|
declare function runScan(opts?: RunScanOptions): ScanResult[];
|
|
331
476
|
declare function writeScanResult(result: ScanResult): void;
|
|
332
477
|
|
|
478
|
+
/**
|
|
479
|
+
* Applies the machine-generated fixes attached to diagnostics.
|
|
480
|
+
*
|
|
481
|
+
* Edits are offset-based against the content the scanner read, so this must
|
|
482
|
+
* run before anything else touches the files. Within a file, edits apply
|
|
483
|
+
* last-to-first; an edit overlapping one already applied is skipped. Identical
|
|
484
|
+
* edits are deduped — two fixes in one file emitting the same edit collapse to
|
|
485
|
+
* one application.
|
|
486
|
+
*/
|
|
487
|
+
interface AppliedFix {
|
|
488
|
+
code: string;
|
|
489
|
+
description: string;
|
|
490
|
+
file?: string;
|
|
491
|
+
}
|
|
492
|
+
interface ApplyFixesResult {
|
|
493
|
+
applied: AppliedFix[];
|
|
494
|
+
/** fixes skipped because of overlapping edits or existing target files */
|
|
495
|
+
skipped: Array<AppliedFix & {
|
|
496
|
+
reason: string;
|
|
497
|
+
}>;
|
|
498
|
+
}
|
|
499
|
+
declare function applyFixes(diagnostics: Diagnostic[]): ApplyFixesResult;
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Cross-file id rename. The registry knows every reference surface for an
|
|
503
|
+
* id — the DOM attribute, `uidex("…")` calls in flow specs, the widget
|
|
504
|
+
* export's discriminator, and `widgets:` arrays — so the rename rewrites all
|
|
505
|
+
* of them in one pass and regenerates the gen file. Occurrences the scanner
|
|
506
|
+
* can't edit mechanically (const indirection, ternaries, patterns) are
|
|
507
|
+
* reported for manual follow-up instead of being guessed at.
|
|
508
|
+
*/
|
|
509
|
+
type RenameKind = "element" | "widget" | "region";
|
|
510
|
+
interface RenameOptions {
|
|
511
|
+
cwd: string;
|
|
512
|
+
kind: RenameKind;
|
|
513
|
+
oldId: string;
|
|
514
|
+
newId: string;
|
|
515
|
+
/** allow renaming onto an id that already exists */
|
|
516
|
+
force?: boolean;
|
|
517
|
+
}
|
|
518
|
+
interface RenameResult {
|
|
519
|
+
/** number of source edits applied */
|
|
520
|
+
edits: number;
|
|
521
|
+
/** occurrences that need a human (no static literal to rewrite) */
|
|
522
|
+
manual: Array<{
|
|
523
|
+
file: string;
|
|
524
|
+
line: number;
|
|
525
|
+
reason: string;
|
|
526
|
+
}>;
|
|
527
|
+
errors: string[];
|
|
528
|
+
}
|
|
529
|
+
declare function renameEntity(opts: RenameOptions): RenameResult;
|
|
530
|
+
|
|
333
531
|
interface CliResult {
|
|
334
532
|
exitCode: number;
|
|
335
533
|
stdout: string;
|
|
@@ -341,7 +539,6 @@ interface CliOptions {
|
|
|
341
539
|
}
|
|
342
540
|
declare function run(opts: CliOptions): Promise<CliResult>;
|
|
343
541
|
|
|
344
|
-
declare const DEFAULT_TYPE_MODE: TypeMode;
|
|
345
542
|
declare class ConfigError extends Error {
|
|
346
543
|
constructor(message: string);
|
|
347
544
|
}
|
|
@@ -402,4 +599,4 @@ declare namespace Uidex {
|
|
|
402
599
|
}
|
|
403
600
|
}
|
|
404
601
|
|
|
405
|
-
export { type Annotation, type AnnotationKind, type AuditConfig, type AuditSummary, CONFIG_FILENAME, type CliOptions, type CliResult, ConfigError, type ConventionsConfig, DEFAULT_CONVENTIONS,
|
|
602
|
+
export { type Annotation, type AnnotationKind, type AppliedFix, type ApplyFixesResult, type AuditConfig, type AuditSummary, CONFIG_FILENAME, type CliOptions, type CliResult, ConfigError, type ConventionsConfig, DEFAULT_CONVENTIONS, type DetectedRoute, type Diagnostic, type DiagnosticFix, type DiagnosticSeverity, type DiscoveredConfig, type ExtractedFile, type FixEdit, type GitContext, type ImportFact, type LandmarkFact, type MetadataExport, type MetadataExportKind, type RenameKind, type RenameOptions, type RenameResult, type RunScanOptions, type ScaffoldKind, type ScaffoldOptions, type ScaffoldResult, type ScaffoldSpecOptions, type ScanResult, type ScannedFile, type SourceConfig, type Span, Uidex, type UidexConfig, applyFixes, audit, detectRoutes, discover, emit, extract, extractUidexExports, globToRegExp, parseConfig, pathToId, renameEntity, resolve, resolveGitContext, run as runCli, runScan, scaffoldSpec, scaffoldWidgetSpec, validateConfig, walk, writeScanResult };
|