@markuplint/pretenders 5.0.0-rc.4 → 5.0.0-rc.6
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/CHANGELOG.md +14 -0
- package/README.md +83 -5
- package/lib/auto-scan.d.ts +36 -0
- package/lib/auto-scan.js +137 -0
- package/lib/cli.d.ts +0 -10
- package/lib/cli.js +1 -11
- package/lib/create-scanner.d.ts +1 -6
- package/lib/create-scanner.js +1 -6
- package/lib/dependency-mapper.d.ts +40 -13
- package/lib/dependency-mapper.js +222 -16
- package/lib/disambiguate.d.ts +37 -0
- package/lib/disambiguate.js +102 -0
- package/lib/export-table.d.ts +45 -0
- package/lib/export-table.js +106 -0
- package/lib/import-resolver/analyze-jsx-imports.d.ts +31 -0
- package/lib/import-resolver/analyze-jsx-imports.js +99 -0
- package/lib/import-resolver/extract-script-source.d.ts +6 -30
- package/lib/import-resolver/extract-script-source.js +6 -26
- package/lib/import-resolver/index.d.ts +1 -0
- package/lib/import-resolver/index.js +15 -18
- package/lib/import-resolver/parse-imports.d.ts +0 -5
- package/lib/import-resolver/parse-imports.js +1 -20
- package/lib/import-resolver/resolve-barrel.js +0 -7
- package/lib/import-resolver/resolve-module-file.d.ts +47 -0
- package/lib/import-resolver/resolve-module-file.js +138 -0
- package/lib/index.d.ts +39 -0
- package/lib/index.js +47 -0
- package/lib/input.d.ts +0 -7
- package/lib/input.js +0 -7
- package/lib/jsx/compiler-host.d.ts +29 -0
- package/lib/jsx/compiler-host.js +56 -0
- package/lib/jsx/create-identify.d.ts +3 -9
- package/lib/jsx/create-identify.js +3 -9
- package/lib/jsx/finder.d.ts +0 -8
- package/lib/jsx/finder.js +2 -16
- package/lib/jsx/get-attributes.d.ts +2 -7
- package/lib/jsx/get-attributes.js +2 -7
- package/lib/jsx/get-children.d.ts +0 -7
- package/lib/jsx/get-children.js +0 -10
- package/lib/jsx/index.js +20 -15
- package/lib/out.d.ts +6 -6
- package/lib/out.js +16 -7
- package/lib/pretender-director.d.ts +24 -24
- package/lib/pretender-director.js +22 -21
- package/lib/scan.d.ts +5 -0
- package/lib/scan.js +5 -2
- package/lib/scanner-loader.d.ts +0 -6
- package/lib/scanner-loader.js +0 -18
- package/lib/template/derive-name.d.ts +0 -6
- package/lib/template/derive-name.js +0 -6
- package/lib/template/detect-slots.d.ts +0 -6
- package/lib/template/detect-slots.js +0 -6
- package/lib/template/extract-attrs.d.ts +0 -5
- package/lib/template/extract-attrs.js +0 -5
- package/lib/template/extract-root.d.ts +1 -6
- package/lib/template/extract-root.js +1 -6
- package/lib/template/index.js +20 -8
- package/lib/template/parse-component.d.ts +0 -16
- package/lib/template/parse-component.js +0 -21
- package/lib/template/types.d.ts +0 -3
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [5.0.0-rc.6](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.5...v5.0.0-rc.6) (2026-08-30)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @markuplint/pretenders
|
|
9
|
+
|
|
10
|
+
# [5.0.0-rc.5](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2026-08-28)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **pretenders:** resolve same-named components via imports, not scan order ([#3957](https://github.com/markuplint/markuplint/issues/3957)) ([d46a514](https://github.com/markuplint/markuplint/commit/d46a5148c4d7afb156962f4ed795f40a9324e6c5)), closes [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3951](https://github.com/markuplint/markuplint/issues/3951)
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- add `pretenders.auto` for on-demand import-graph resolution ([#3962](https://github.com/markuplint/markuplint/issues/3962)) ([5870671](https://github.com/markuplint/markuplint/commit/58706711a20c12cff080d49359f3f6443345eca3)), closes [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3959](https://github.com/markuplint/markuplint/issues/3959) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3959](https://github.com/markuplint/markuplint/issues/3959) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3951](https://github.com/markuplint/markuplint/issues/3951)
|
|
19
|
+
|
|
6
20
|
# [5.0.0-rc.4](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-04-19)
|
|
7
21
|
|
|
8
22
|
**Note:** Version bump only for package @markuplint/pretenders
|
package/README.md
CHANGED
|
@@ -49,6 +49,21 @@ Instead of the CLI, you can configure dynamic scanning directly in your markupli
|
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### On-demand Scanning
|
|
53
|
+
|
|
54
|
+
`scan` pre-scans a configured set of files once. Alternatively, the `auto` field resolves pretenders per lint target by walking the linted file's own import graph — no `files`/`scan` configuration needed, and same-named components in unrelated files can't collide since only files the linted file actually imports (transitively) are ever considered:
|
|
55
|
+
|
|
56
|
+
```jsonc
|
|
57
|
+
// .markuplintrc
|
|
58
|
+
{
|
|
59
|
+
"pretenders": {
|
|
60
|
+
"auto": true,
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Only the config file is filesystem-watched, so in watch mode or an editor session, results can go stale if an imported component file changes without the config changing too.
|
|
66
|
+
|
|
52
67
|
## How It Works
|
|
53
68
|
|
|
54
69
|
### JSX Scanner
|
|
@@ -121,20 +136,25 @@ Slot detection covers:
|
|
|
121
136
|
|
|
122
137
|
### Import Resolver
|
|
123
138
|
|
|
124
|
-
The import resolver analyzes
|
|
125
|
-
|
|
126
|
-
Script source extraction is delegated to each parser's component-scanner (Vue, Svelte, Astro), while MDX extraction is built-in. Supported script block types:
|
|
139
|
+
The import resolver analyzes a component file and extracts import bindings, linking component usage to source file locations for cross-file dependency resolution.
|
|
127
140
|
|
|
128
141
|
- Vue `<script setup>` (via `@markuplint/vue-parser/component-scanner`)
|
|
129
142
|
- Vue Options API `<script>` (fallback when no `<script setup>`; only imports registered in `components: { ... }` are returned)
|
|
130
143
|
- Svelte `<script>` (via `@markuplint/svelte-parser/component-scanner`; prefers instance script over module script)
|
|
131
144
|
- Astro frontmatter (via `@markuplint/astro-parser/component-scanner`)
|
|
132
145
|
- MDX top-level ESM (built-in)
|
|
146
|
+
- JS/TS/JSX/TSX (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts`): analyzed directly via the TypeScript AST, so JSX syntax and non-standard TS constructs (which `es-module-lexer` cannot parse) are handled correctly
|
|
133
147
|
|
|
134
148
|
Dynamic imports with string literal specifiers (`import('./path')`) are included in bindings with `type: 'dynamic'`. Template literal and variable specifiers are excluded.
|
|
135
149
|
|
|
136
150
|
Barrel file re-exports can be resolved with `resolveBarrelExport`, which maps a named import from a directory with an index file back to its original source module (single-level only).
|
|
137
151
|
|
|
152
|
+
### Lint-time Disambiguation
|
|
153
|
+
|
|
154
|
+
When the same component name is declared in more than one scanned file (e.g., two unrelated `Item` components rendering different elements), the generated pretenders keep independent entries for each — but `markuplint`'s lint pipeline still needs to know, for the specific file being linted, which one it actually refers to. `disambiguatePretenders` resolves this from the lint target's own declarations and imports; it's normally invoked automatically by `markuplint`'s config resolution, not called directly.
|
|
155
|
+
|
|
156
|
+
In long-running hosts (watch mode, editor extensions) that keep re-resolving pretenders across file edits, call `clearPretenderCaches()` after each edit — otherwise a renamed export or a newly valid tsconfig `paths` alias keeps resolving as it did before the change for the rest of the process's lifetime.
|
|
157
|
+
|
|
138
158
|
## API
|
|
139
159
|
|
|
140
160
|
### `scan(files, options)`
|
|
@@ -210,9 +230,9 @@ const pretenders = await templateScanner(
|
|
|
210
230
|
|
|
211
231
|
### `analyzeImports(filePath, source)`
|
|
212
232
|
|
|
213
|
-
Extracts import bindings from a component file
|
|
233
|
+
Extracts import bindings from a component file. Detects the framework from the file extension and extracts the appropriate source automatically — `.vue`, `.svelte`, `.astro`, and `.mdx` extract a script/frontmatter/ESM block first, while `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, and `.cts` are analyzed directly via the TypeScript AST (which, unlike `es-module-lexer`, can parse JSX syntax).
|
|
214
234
|
|
|
215
|
-
Returns `null` if the file extension is not
|
|
235
|
+
Returns `null` if the file extension is not one of the above.
|
|
216
236
|
|
|
217
237
|
```ts
|
|
218
238
|
import { analyzeImports } from '@markuplint/pretenders';
|
|
@@ -281,3 +301,61 @@ if (binding) {
|
|
|
281
301
|
#### Returns
|
|
282
302
|
|
|
283
303
|
`string | null` — The relative source path from the barrel file, or `null` if not a barrel or name not found.
|
|
304
|
+
|
|
305
|
+
### `disambiguatePretenders(pretenders, options)`
|
|
306
|
+
|
|
307
|
+
Given a flat pretender list and the file currently being linted, resolves which of several same-selector candidates that file actually refers to. Entries without a `filePath`, or whose `selector` isn't a plain identifier, are never touched. Normally invoked automatically as part of `markuplint`'s config resolution.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { disambiguatePretenders } from '@markuplint/pretenders';
|
|
311
|
+
|
|
312
|
+
const resolved = await disambiguatePretenders(pretenders, {
|
|
313
|
+
filePath: '/absolute/path/to/Page.tsx',
|
|
314
|
+
sourceCode: source,
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Parameters
|
|
319
|
+
|
|
320
|
+
| Parameter | Type | Description |
|
|
321
|
+
| -------------------- | ---------------------- | ------------------------------------------- |
|
|
322
|
+
| `pretenders` | `readonly Pretender[]` | The resolved pretender list to disambiguate |
|
|
323
|
+
| `options.filePath` | `string` | Absolute path of the file being linted |
|
|
324
|
+
| `options.sourceCode` | `string` | Full source text of the file being linted |
|
|
325
|
+
|
|
326
|
+
#### Returns
|
|
327
|
+
|
|
328
|
+
`Promise<readonly Pretender[]>` — The disambiguated pretender list, or `pretenders` itself when there was nothing to resolve or nothing could be confirmed.
|
|
329
|
+
|
|
330
|
+
### `autoScan(entryAbsPath, sourceCode)`
|
|
331
|
+
|
|
332
|
+
Resolves pretenders on demand by walking a single lint target's own import graph (breadth-first, extension-agnostic — a `.tsx` entry can import a `.vue` file and vice versa) and scanning the collected files in one batch. This is the resolution logic behind the `pretenders.auto` config option; it's normally invoked automatically as part of `markuplint`'s config resolution, not called directly.
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
import { autoScan } from '@markuplint/pretenders';
|
|
336
|
+
|
|
337
|
+
const pretenders = await autoScan('/absolute/path/to/Page.tsx', sourceCode);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### Parameters
|
|
341
|
+
|
|
342
|
+
| Parameter | Type | Description |
|
|
343
|
+
| -------------- | -------- | ------------------------------------------------------------- |
|
|
344
|
+
| `entryAbsPath` | `string` | Absolute path of the file being linted |
|
|
345
|
+
| `sourceCode` | `string` | The entry file's current text (may be unsaved editor content) |
|
|
346
|
+
|
|
347
|
+
#### Returns
|
|
348
|
+
|
|
349
|
+
`Promise<Pretender[]>` — Discovered pretender mappings for the entry file and its import graph.
|
|
350
|
+
|
|
351
|
+
Results are cached per entry path, keyed on `sourceCode` equality (not mtime, which doesn't exist for unsaved editor content); `node_modules` is never traversed into, and import cycles are handled via a visited set. Traversal is capped at 8 import hops for template-language (`.vue`/`.svelte`/`.astro`) chains, but this cap does not hold for JSX/TSX-only chains: `jsxScanner` builds a `ts.Program` from the collected files, and TypeScript's own module resolution transitively pulls in whatever those files import regardless of the cap.
|
|
352
|
+
|
|
353
|
+
### `clearPretenderCaches()`
|
|
354
|
+
|
|
355
|
+
Clears the module-level caches that back import/export resolution (module resolution, export tables, parsed JSX source files, `autoScan` results). None of these caches expire on their own, so a long-running host (watch mode, an editor extension) that keeps resolving pretenders across file edits must call this after each edit — otherwise a renamed export or a newly valid tsconfig `paths` alias keeps resolving as it did before the change for the rest of the process's lifetime.
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import { clearPretenderCaches } from '@markuplint/pretenders';
|
|
359
|
+
|
|
360
|
+
clearPretenderCaches();
|
|
361
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module auto-scan
|
|
3
|
+
*
|
|
4
|
+
* On-demand pretender resolution: given a single lint target's absolute path
|
|
5
|
+
* and current source text, walks its import graph (breadth-first) to collect
|
|
6
|
+
* the transitively-referenced component files, then scans all of them in one
|
|
7
|
+
* batch. This lets `pretenders: { auto: true }` work without any
|
|
8
|
+
* pre-configured `files`/`scan` glob, at the cost of running a scan per lint
|
|
9
|
+
* target instead of once per project (see `jsx/compiler-host.ts` for the
|
|
10
|
+
* content-based caching that offsets this).
|
|
11
|
+
*
|
|
12
|
+
* Traversal is extension-agnostic — a `.tsx` entry can import a `.vue` file
|
|
13
|
+
* and vice versa, even though TypeScript's own module resolution can't
|
|
14
|
+
* follow into `.vue` (it falls back to a filesystem search; see
|
|
15
|
+
* `resolveModuleFile`). Per-extension scanner dispatch only happens at the
|
|
16
|
+
* final `scan()` call.
|
|
17
|
+
*/
|
|
18
|
+
import type { Pretender } from '@markuplint/ml-config';
|
|
19
|
+
/**
|
|
20
|
+
* Clears the module-level auto-scan result cache. This cache is keyed on the
|
|
21
|
+
* ENTRY file's path and content only, so an edit to any other file in the
|
|
22
|
+
* entry's import graph does not invalidate it — a long-running host must call
|
|
23
|
+
* `clearPretenderCaches()` (which includes this one) after an edit to any file
|
|
24
|
+
* `autoScan` may have walked, not just after an edit to the entry itself.
|
|
25
|
+
*/
|
|
26
|
+
export declare function clearAutoScanCache(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Resolves pretenders on demand by scanning `entryAbsPath`'s own import graph:
|
|
29
|
+
* the entry file plus every file it transitively imports (up to a fixed
|
|
30
|
+
* traversal depth), scanned together in one `scan()` call.
|
|
31
|
+
*
|
|
32
|
+
* @param entryAbsPath - Absolute path of the file currently being linted
|
|
33
|
+
* @param sourceCode - The entry file's current text (may be unsaved editor content)
|
|
34
|
+
* @returns Discovered pretender mappings for the entry file and its import graph
|
|
35
|
+
*/
|
|
36
|
+
export declare function autoScan(entryAbsPath: string, sourceCode: string): Promise<Pretender[]>;
|
package/lib/auto-scan.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module auto-scan
|
|
3
|
+
*
|
|
4
|
+
* On-demand pretender resolution: given a single lint target's absolute path
|
|
5
|
+
* and current source text, walks its import graph (breadth-first) to collect
|
|
6
|
+
* the transitively-referenced component files, then scans all of them in one
|
|
7
|
+
* batch. This lets `pretenders: { auto: true }` work without any
|
|
8
|
+
* pre-configured `files`/`scan` glob, at the cost of running a scan per lint
|
|
9
|
+
* target instead of once per project (see `jsx/compiler-host.ts` for the
|
|
10
|
+
* content-based caching that offsets this).
|
|
11
|
+
*
|
|
12
|
+
* Traversal is extension-agnostic — a `.tsx` entry can import a `.vue` file
|
|
13
|
+
* and vice versa, even though TypeScript's own module resolution can't
|
|
14
|
+
* follow into `.vue` (it falls back to a filesystem search; see
|
|
15
|
+
* `resolveModuleFile`). Per-extension scanner dispatch only happens at the
|
|
16
|
+
* final `scan()` call.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { isFatalError } from '@markuplint/shared';
|
|
21
|
+
import { analyzeImports } from './import-resolver/index.js';
|
|
22
|
+
import { normalizePath, resolveModuleFile } from './import-resolver/resolve-module-file.js';
|
|
23
|
+
import { scan } from './scan.js';
|
|
24
|
+
const SCANNABLE_EXTENSIONS = new Set([
|
|
25
|
+
'.js',
|
|
26
|
+
'.jsx',
|
|
27
|
+
'.ts',
|
|
28
|
+
'.tsx',
|
|
29
|
+
'.mjs',
|
|
30
|
+
'.cjs',
|
|
31
|
+
'.mts',
|
|
32
|
+
'.cts',
|
|
33
|
+
'.vue',
|
|
34
|
+
'.svelte',
|
|
35
|
+
'.astro',
|
|
36
|
+
]);
|
|
37
|
+
// Bounds runaway traversal (a deep or wrongly resolved chain) rather than
|
|
38
|
+
// expressing a real limit on legitimate component nesting. Note this only
|
|
39
|
+
// bounds which files BFS explicitly visits: for JSX/TSX files, jsxScanner
|
|
40
|
+
// builds a ts.Program from the collected file list, and TypeScript's own
|
|
41
|
+
// module resolution transitively pulls in whatever those files import —
|
|
42
|
+
// including files past this depth — so the cap is not a hard ceiling on
|
|
43
|
+
// what ends up scanned when the chain is JSX/TSX throughout.
|
|
44
|
+
const MAX_DEPTH = 8;
|
|
45
|
+
const resultCache = new Map();
|
|
46
|
+
/**
|
|
47
|
+
* Clears the module-level auto-scan result cache. This cache is keyed on the
|
|
48
|
+
* ENTRY file's path and content only, so an edit to any other file in the
|
|
49
|
+
* entry's import graph does not invalidate it — a long-running host must call
|
|
50
|
+
* `clearPretenderCaches()` (which includes this one) after an edit to any file
|
|
51
|
+
* `autoScan` may have walked, not just after an edit to the entry itself.
|
|
52
|
+
*/
|
|
53
|
+
export function clearAutoScanCache() {
|
|
54
|
+
resultCache.clear();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolves pretenders on demand by scanning `entryAbsPath`'s own import graph:
|
|
58
|
+
* the entry file plus every file it transitively imports (up to a fixed
|
|
59
|
+
* traversal depth), scanned together in one `scan()` call.
|
|
60
|
+
*
|
|
61
|
+
* @param entryAbsPath - Absolute path of the file currently being linted
|
|
62
|
+
* @param sourceCode - The entry file's current text (may be unsaved editor content)
|
|
63
|
+
* @returns Discovered pretender mappings for the entry file and its import graph
|
|
64
|
+
*/
|
|
65
|
+
export async function autoScan(entryAbsPath, sourceCode) {
|
|
66
|
+
const entryKey = normalizePath(entryAbsPath);
|
|
67
|
+
const cached = resultCache.get(entryKey);
|
|
68
|
+
if (cached && cached.sourceCode === sourceCode) {
|
|
69
|
+
return cached.pretenders;
|
|
70
|
+
}
|
|
71
|
+
const sources = new Map([[entryKey, sourceCode]]);
|
|
72
|
+
const visited = new Set([entryKey]);
|
|
73
|
+
const collected = [];
|
|
74
|
+
if (isScannable(entryAbsPath)) {
|
|
75
|
+
collected.push(entryAbsPath);
|
|
76
|
+
}
|
|
77
|
+
let frontier = [
|
|
78
|
+
{ absPath: entryAbsPath, source: sourceCode },
|
|
79
|
+
];
|
|
80
|
+
for (let depth = 0; depth < MAX_DEPTH && frontier.length > 0; depth++) {
|
|
81
|
+
const nextFrontier = [];
|
|
82
|
+
for (const { absPath, source } of frontier) {
|
|
83
|
+
const analysis = await analyzeImports(absPath, source);
|
|
84
|
+
if (!analysis) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
for (const binding of analysis.bindings) {
|
|
88
|
+
const resolved = resolveModuleFile(absPath, binding.source);
|
|
89
|
+
if (!resolved) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const key = normalizePath(resolved);
|
|
93
|
+
if (visited.has(key) || key.includes('/node_modules/')) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
visited.add(key);
|
|
97
|
+
if (isDeclarationFile(resolved) || !isScannable(resolved)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
collected.push(resolved);
|
|
101
|
+
const childSource = readFileSafe(resolved);
|
|
102
|
+
if (childSource == null) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Feed this read into `sources` so the `scan()` call below reuses it
|
|
106
|
+
// instead of hitting disk again — both the scanners' own file reads
|
|
107
|
+
// and the export-table construction behind same-selector
|
|
108
|
+
// disambiguation consult `sources` first.
|
|
109
|
+
sources.set(key, childSource);
|
|
110
|
+
nextFrontier.push({ absPath: resolved, source: childSource });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
frontier = nextFrontier;
|
|
114
|
+
}
|
|
115
|
+
const pretenders = await scan(collected, { sources });
|
|
116
|
+
resultCache.set(entryKey, { sourceCode, pretenders });
|
|
117
|
+
return pretenders;
|
|
118
|
+
}
|
|
119
|
+
function isScannable(filePath) {
|
|
120
|
+
return SCANNABLE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
121
|
+
}
|
|
122
|
+
const DECLARATION_SUFFIXES = ['.d.ts', '.d.mts', '.d.cts'];
|
|
123
|
+
function isDeclarationFile(filePath) {
|
|
124
|
+
const lower = filePath.toLowerCase();
|
|
125
|
+
return DECLARATION_SUFFIXES.some(suffix => lower.endsWith(suffix));
|
|
126
|
+
}
|
|
127
|
+
function readFileSafe(filePath) {
|
|
128
|
+
try {
|
|
129
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (isFatalError(error)) {
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
package/lib/cli.d.ts
CHANGED
|
@@ -1,11 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI entry point for the pretenders scanner.
|
|
3
|
-
* Accepts file glob patterns as input arguments and writes discovered
|
|
4
|
-
* pretender mappings to a JSON output file.
|
|
5
|
-
*
|
|
6
|
-
* Usage: pretenders [options] <glob patterns...>
|
|
7
|
-
* Flags:
|
|
8
|
-
* -O, --out <path> Output file path (required)
|
|
9
|
-
* --ignore <names> Comma-separated list of component names to ignore
|
|
10
|
-
*/
|
|
11
1
|
export {};
|
package/lib/cli.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI entry point for the pretenders scanner.
|
|
3
|
-
* Accepts file glob patterns as input arguments and writes discovered
|
|
4
|
-
* pretender mappings to a JSON output file.
|
|
5
|
-
*
|
|
6
|
-
* Usage: pretenders [options] <glob patterns...>
|
|
7
|
-
* Flags:
|
|
8
|
-
* -O, --out <path> Output file path (required)
|
|
9
|
-
* --ignore <names> Comma-separated list of component names to ignore
|
|
10
|
-
*/
|
|
11
1
|
import path from 'node:path';
|
|
12
2
|
import meow from 'meow';
|
|
13
3
|
import { getFileList } from './input.js';
|
|
@@ -35,6 +25,6 @@ async function main() {
|
|
|
35
25
|
ignoreComponentNames: commands.flags.ignore?.split(',').map(s => s.trim()),
|
|
36
26
|
});
|
|
37
27
|
const outFilePath = path.resolve(process.cwd(), commands.flags.out);
|
|
38
|
-
await out(outFilePath, pretenders);
|
|
28
|
+
await out(outFilePath, pretenders, process.cwd());
|
|
39
29
|
}
|
|
40
30
|
await main();
|
package/lib/create-scanner.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { PretenderScannerScanMethod } from './types.js';
|
|
2
2
|
import type { PretenderScanOptions } from '@markuplint/ml-config';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Ensures all provided file paths are absolute before delegating to the scan method.
|
|
6
|
-
*
|
|
7
|
-
* @template O - The scan options type, extending PretenderScanOptions
|
|
8
|
-
* @param method - The underlying scan method to wrap with validation
|
|
9
|
-
* @returns A wrapped scanner function that validates file paths and then invokes the scan method
|
|
4
|
+
* Wraps a scan method to enforce that all file paths are absolute before delegating.
|
|
10
5
|
*/
|
|
11
6
|
export declare function createScanner<O extends PretenderScanOptions = PretenderScanOptions>(method: PretenderScannerScanMethod<O>): (files: readonly string[], options?: O) => Promise<import("@markuplint/ml-config").Pretender[]>;
|
package/lib/create-scanner.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Ensures all provided file paths are absolute before delegating to the scan method.
|
|
5
|
-
*
|
|
6
|
-
* @template O - The scan options type, extending PretenderScanOptions
|
|
7
|
-
* @param method - The underlying scan method to wrap with validation
|
|
8
|
-
* @returns A wrapped scanner function that validates file paths and then invokes the scan method
|
|
3
|
+
* Wraps a scan method to enforce that all file paths are absolute before delegating.
|
|
9
4
|
*/
|
|
10
5
|
export function createScanner(method) {
|
|
11
6
|
return (files, options) => {
|
|
@@ -1,25 +1,52 @@
|
|
|
1
1
|
import type { PretenderDirectorMap } from './pretender-director.js';
|
|
2
2
|
import type { Identifier } from './types.js';
|
|
3
|
+
import type { ImportBinding } from './import-resolver/types.js';
|
|
3
4
|
import type { Pretender } from '@markuplint/ml-config';
|
|
5
|
+
export interface DependencyMapperContext {
|
|
6
|
+
/** Import bindings found in each source file, keyed by the same relative path used as `sourceFile`. */
|
|
7
|
+
readonly importsByFile?: ReadonlyMap<string, readonly ImportBinding[]>;
|
|
8
|
+
/** Base directory that relative `sourceFile` paths (and resolved module paths) are relative to. */
|
|
9
|
+
readonly cwd?: string;
|
|
10
|
+
/**
|
|
11
|
+
* In-memory content overrides (keyed by normalized absolute path), consulted
|
|
12
|
+
* before falling back to disk when resolving a chased import's export table.
|
|
13
|
+
* Without this, a file with unsaved content (e.g. the lint target itself,
|
|
14
|
+
* when reached via a circular import) resolves against its stale on-disk
|
|
15
|
+
* version instead — which can silently pick a wrong same-named component
|
|
16
|
+
* (see the module JSDoc on `getExportTableForFile`).
|
|
17
|
+
*/
|
|
18
|
+
readonly sources?: ReadonlyMap<string, string>;
|
|
19
|
+
}
|
|
4
20
|
/**
|
|
5
|
-
* Resolves a map of component-to-identity mappings into a flat array of Pretender objects.
|
|
6
21
|
* Follows chains where one component wraps another (e.g., MyButton -> Button -> button)
|
|
7
22
|
* until a native element is reached or a cycle is detected.
|
|
8
23
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
24
|
+
* Resolution order at each hop:
|
|
25
|
+
* 1. A same-file local declaration (the file the reference itself lives in).
|
|
26
|
+
* 2. The file's recorded import bindings, resolved to the actual declaration file
|
|
27
|
+
* via TypeScript module resolution and the target file's export table.
|
|
28
|
+
* 3. The legacy name-based index, for backward compatibility when no file context
|
|
29
|
+
* is available (or the reference can't be resolved through it).
|
|
11
30
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
31
|
+
* (1) and (2) are what let same-named components declared in different files
|
|
32
|
+
* (e.g., two unrelated `Item` components) resolve independently instead of the
|
|
33
|
+
* first-registered one silently winning for every reference — see issue #3951.
|
|
15
34
|
*/
|
|
16
|
-
export declare function dependencyMapper(map: Readonly<PretenderDirectorMap>, nameIndex?: Readonly<Map<Identifier, string
|
|
35
|
+
export declare function dependencyMapper(map: Readonly<PretenderDirectorMap>, nameIndex?: Readonly<Map<Identifier, string>>, context?: DependencyMapperContext): Pretender[];
|
|
17
36
|
/**
|
|
18
|
-
*
|
|
37
|
+
* Clears the module-level export-table cache. A table built from a file's
|
|
38
|
+
* on-disk content never expires on its own, so a long-running host (a
|
|
39
|
+
* watch-mode lint run, an editor extension) that keeps resolving pretenders
|
|
40
|
+
* across file edits must call this whenever it re-resolves without cache (e.g.
|
|
41
|
+
* after a file change) — otherwise a renamed or restructured export keeps
|
|
42
|
+
* resolving to what it used to be for the rest of the process's lifetime.
|
|
19
43
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @returns A comparator function for use with `Array.prototype.sort()`
|
|
44
|
+
* Tables built from an in-memory `sources` override are the exception: their
|
|
45
|
+
* text is already in memory, so comparing it costs nothing and they rebuild as
|
|
46
|
+
* soon as it differs.
|
|
24
47
|
*/
|
|
25
|
-
export declare function
|
|
48
|
+
export declare function clearExportTableCache(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Comparator that sorts by `propName`, case-insensitive for string values.
|
|
51
|
+
*/
|
|
52
|
+
export declare function propSort<T, P extends keyof T>(propName: P): (a: T, b: T) => 0 | 1 | -1;
|