@markuplint/pretenders 5.0.0-dev.5 → 5.0.0-rc.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/CHANGELOG.md +25 -0
- package/README.md +207 -62
- package/lib/cli.js +2 -3
- package/lib/dependency-mapper.d.ts +18 -35
- package/lib/dependency-mapper.js +40 -50
- package/lib/import-resolver/extract-script-source.d.ts +82 -0
- package/lib/import-resolver/extract-script-source.js +227 -0
- package/lib/import-resolver/index.d.ts +85 -0
- package/lib/import-resolver/index.js +184 -0
- package/lib/import-resolver/parse-imports.d.ts +19 -0
- package/lib/import-resolver/parse-imports.js +194 -0
- package/lib/import-resolver/resolve-barrel.d.ts +19 -0
- package/lib/import-resolver/resolve-barrel.js +113 -0
- package/lib/import-resolver/types.d.ts +34 -0
- package/lib/import-resolver/types.js +1 -0
- package/lib/index.d.ts +26 -1
- package/lib/index.js +24 -1
- package/lib/jsx/create-identify.d.ts +3 -4
- package/lib/jsx/create-identify.js +7 -22
- package/lib/jsx/get-children.d.ts +12 -6
- package/lib/jsx/get-children.js +64 -8
- package/lib/jsx/index.d.ts +4 -0
- package/lib/jsx/index.js +13 -5
- package/lib/pretender-director.d.ts +13 -4
- package/lib/pretender-director.js +15 -6
- package/lib/scan.d.ts +20 -0
- package/lib/scan.js +24 -0
- package/lib/template/derive-name.d.ts +12 -0
- package/lib/template/derive-name.js +27 -0
- package/lib/template/detect-slots.d.ts +14 -0
- package/lib/template/detect-slots.js +23 -0
- package/lib/template/extract-attrs.d.ts +13 -0
- package/lib/template/extract-attrs.js +26 -0
- package/lib/template/extract-root.d.ts +11 -0
- package/lib/template/extract-root.js +17 -0
- package/lib/template/index.d.ts +13 -0
- package/lib/template/index.js +51 -0
- package/lib/template/parse-component.d.ts +22 -0
- package/lib/template/parse-component.js +74 -0
- package/lib/template/types.d.ts +6 -0
- package/lib/template/types.js +1 -0
- package/lib/types.d.ts +7 -0
- package/package.json +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,31 @@
|
|
|
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.0](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.3...v5.0.0-rc.0) (2026-03-12)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **pretenders,ml-config:** add scan field to JSON schema and narrow getParser error handling ([e5fda17](https://github.com/markuplint/markuplint/commit/e5fda171f886c1b65d6f0e536c182cebb279ea07))
|
|
11
|
+
- **pretenders:** add error handling to parseComponent and resolveBarrelExport ([c203c4b](https://github.com/markuplint/markuplint/commit/c203c4be341c89abfd0ba7991f09d8322d17728e))
|
|
12
|
+
- **pretenders:** address QA review findings for import resolver phase 2 ([d8fd73f](https://github.com/markuplint/markuplint/commit/d8fd73f533e9b1febec05d93f144f2d5122cffc9))
|
|
13
|
+
- **pretenders:** align import-resolver with [#3335](https://github.com/markuplint/markuplint/issues/3335) design spec ([448181a](https://github.com/markuplint/markuplint/commit/448181ac819e279c925e68c8f5577c7a7cd8ae61)), closes [#3339](https://github.com/markuplint/markuplint/issues/3339) [#3340](https://github.com/markuplint/markuplint/issues/3340)
|
|
14
|
+
- **pretenders:** fix ensureInit TOCTOU race condition ([d9643d8](https://github.com/markuplint/markuplint/commit/d9643d8b679e888b143d9d47f243e35fbeeb6752))
|
|
15
|
+
- **pretenders:** fix false positives in children detection and harden tests ([96f7af3](https://github.com/markuplint/markuplint/commit/96f7af3adf4d6a3b8c65a9e9464cf1bf34c48613))
|
|
16
|
+
- **pretenders:** guard dynamic parser imports and improve test coverage ([9667aa1](https://github.com/markuplint/markuplint/commit/9667aa1278a3ac737fa8c6cfc2df5c1ffd9834c9))
|
|
17
|
+
- **pretenders:** handle empty path edge case in deriveName ([0613743](https://github.com/markuplint/markuplint/commit/0613743c3a83c124b90a5e3f54c62e23b2a7b3bd))
|
|
18
|
+
- **pretenders:** pass importPath in both scanners to prevent name collision ([8644b21](https://github.com/markuplint/markuplint/commit/8644b21b99f1a5a13ab58a4d6d042d2e1aa2a446))
|
|
19
|
+
- **pretenders:** rename createIndentity to createIdentity ([e8f37e5](https://github.com/markuplint/markuplint/commit/e8f37e52f32107e7cc9a9415d2edb6b3bb442fe5))
|
|
20
|
+
- **pretenders:** warn when parser package is not found ([03d567e](https://github.com/markuplint/markuplint/commit/03d567e937228ecfd5b3d9d7811814efd9dd2d09))
|
|
21
|
+
- use visited set for cycle detection in dependencyMapper ([8821f4f](https://github.com/markuplint/markuplint/commit/8821f4fab9cb900582ae0f991e5efe7be413d584)), closes [#3336](https://github.com/markuplint/markuplint/issues/3336)
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
- **pretenders,ml-core:** implement slots detection in JSX scanner and ml-core consumption ([ad9c8e2](https://github.com/markuplint/markuplint/commit/ad9c8e20d233cddc752fce9ad83838857f81787f)), closes [#3341](https://github.com/markuplint/markuplint/issues/3341)
|
|
26
|
+
- **pretenders:** add import-resolver module via es-module-lexer ([19c9f65](https://github.com/markuplint/markuplint/commit/19c9f65b3856613fa2d7bc59cc79d5b829894663)), closes [#3339](https://github.com/markuplint/markuplint/issues/3339)
|
|
27
|
+
- **pretenders:** add MLAST-based templateScanner for Vue/Svelte/Astro ([b710639](https://github.com/markuplint/markuplint/commit/b71063937bd13523a7fef31da2c2f9095674a957)), closes [#3338](https://github.com/markuplint/markuplint/issues/3338)
|
|
28
|
+
- **pretenders:** dispatch CLI input to both JSX and template scanners ([a5535af](https://github.com/markuplint/markuplint/commit/a5535af1e7e496f570cddce52148eb21f9611cfe))
|
|
29
|
+
- **pretenders:** import resolver phase 2 — dynamic imports, Vue Options API, barrel files ([203d4fb](https://github.com/markuplint/markuplint/commit/203d4fb5bfdc0656f95a39af23b5e079ea324d39)), closes [#3359](https://github.com/markuplint/markuplint/issues/3359)
|
|
30
|
+
|
|
6
31
|
# [5.0.0-alpha.3](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.2...v5.0.0-alpha.3) (2026-02-26)
|
|
7
32
|
|
|
8
33
|
**Note:** Version bump only for package @markuplint/pretenders
|
package/README.md
CHANGED
|
@@ -1,18 +1,59 @@
|
|
|
1
1
|
# @markuplint/pretenders
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@markuplint/pretenders)
|
|
4
|
-
[](https://travis-ci.org/markuplint/markuplint)
|
|
5
|
-
[](https://coveralls.io/github/markuplint/markuplint?branch=main)
|
|
6
4
|
|
|
7
|
-
This module features both an API and a CLI that generate **[Pretenders](https://markuplint.dev/docs/guides/besides-html#pretenders) data** from the loaded components
|
|
5
|
+
This module features both an API and a CLI that generate **[Pretenders](https://markuplint.dev/docs/guides/besides-html#pretenders) data** from the loaded components.
|
|
8
6
|
|
|
9
|
-
##
|
|
7
|
+
## Supported Frameworks
|
|
8
|
+
|
|
9
|
+
| Framework | Extensions | Scanner | Approach |
|
|
10
|
+
| ----------- | ---------------------------- | ----------------- | ------------------------------------- |
|
|
11
|
+
| React / JSX | `.js`, `.jsx`, `.ts`, `.tsx` | `jsxScanner` | TypeScript compiler API |
|
|
12
|
+
| Vue | `.vue` | `templateScanner` | MLAST via `@markuplint/vue-parser` |
|
|
13
|
+
| Svelte | `.svelte` | `templateScanner` | MLAST via `@markuplint/svelte-parser` |
|
|
14
|
+
| Astro | `.astro` | `templateScanner` | MLAST via `@markuplint/astro-parser` |
|
|
15
|
+
|
|
16
|
+
## CLI Usage
|
|
10
17
|
|
|
11
18
|
```sh
|
|
12
|
-
$ npx @markuplint/pretenders "./src/**/*.jsx" --out "./pretenders.json"
|
|
19
|
+
$ npx @markuplint/pretenders "./src/**/*.{jsx,tsx,vue,svelte,astro}" --out "./pretenders.json"
|
|
13
20
|
```
|
|
14
21
|
|
|
15
|
-
The
|
|
22
|
+
The CLI accepts glob patterns covering any combination of the supported frameworks. It dispatches files to the appropriate scanner based on file extension, runs them in parallel, and writes the merged results as JSON.
|
|
23
|
+
|
|
24
|
+
| Flag | Description |
|
|
25
|
+
| ------------- | -------------------------------------------------- |
|
|
26
|
+
| `-O`, `--out` | Output file path (required) |
|
|
27
|
+
| `--ignore` | Comma-separated list of component names to exclude |
|
|
28
|
+
|
|
29
|
+
### Configuration-based Scanning
|
|
30
|
+
|
|
31
|
+
Instead of the CLI, you can configure dynamic scanning directly in your markuplint config file. The `scan` field in `pretenders` accepts glob patterns and automatically dispatches to the appropriate scanner. The `files` field accepts either a single glob string or an array of globs:
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
// .markuplintrc
|
|
35
|
+
{
|
|
36
|
+
"pretenders": {
|
|
37
|
+
"scan": [
|
|
38
|
+
{
|
|
39
|
+
// Single glob string
|
|
40
|
+
"files": "./src/components/**/*.{vue,tsx,svelte,astro}",
|
|
41
|
+
"ignoreComponentNames": ["InternalHelper"],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
// Array of glob strings
|
|
45
|
+
"files": ["./src/pages/**/*.tsx", "./src/layouts/**/*.astro"],
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## How It Works
|
|
53
|
+
|
|
54
|
+
### JSX Scanner
|
|
55
|
+
|
|
56
|
+
The JSX scanner analyzes components defined in files using the TypeScript compiler API. It searches for functions or function objects that return elements and maps their function names or the variable names holding these function objects. For example, if a function object named `Foo` returns a `<div>`, the component `Foo` is considered as pretending to be a `div`.
|
|
16
57
|
|
|
17
58
|
```jsx
|
|
18
59
|
const Foo = () => <div />;
|
|
@@ -24,20 +65,12 @@ function Bar() {
|
|
|
24
65
|
|
|
25
66
|
```json
|
|
26
67
|
[
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
"as": "div"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"selector": "Bar",
|
|
33
|
-
"as": "span"
|
|
34
|
-
}
|
|
68
|
+
{ "selector": "Foo", "as": "div" },
|
|
69
|
+
{ "selector": "Bar", "as": "span" }
|
|
35
70
|
]
|
|
36
71
|
```
|
|
37
72
|
|
|
38
|
-
The
|
|
39
|
-
|
|
40
|
-
In addition to definitions based on function and variable names, the module also infers HTML elements from properties, as exemplified by `styled-components`, and infers dependencies from arguments.
|
|
73
|
+
The JSX scanner also infers HTML elements from styled-components patterns and infers dependencies from wrapper function arguments:
|
|
41
74
|
|
|
42
75
|
```jsx
|
|
43
76
|
const Foo = styled.div`
|
|
@@ -49,90 +82,202 @@ const Bar = styled(Foo)`
|
|
|
49
82
|
`;
|
|
50
83
|
```
|
|
51
84
|
|
|
85
|
+
```json
|
|
86
|
+
[
|
|
87
|
+
{ "selector": "Foo", "as": "div" },
|
|
88
|
+
{ "selector": "Bar", "as": "div" }
|
|
89
|
+
]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The JSX scanner detects **slots** (children). If a component accepts `children` props, the resulting pretender includes `slots: true` in its `as` field.
|
|
93
|
+
|
|
94
|
+
### Template Scanner
|
|
95
|
+
|
|
96
|
+
The template scanner uses markuplint's own framework parsers (Vue, Svelte, Astro) to extract the root element from component templates at depth=0. It also detects static attributes and slot/children usage.
|
|
97
|
+
|
|
98
|
+
```vue
|
|
99
|
+
<template>
|
|
100
|
+
<button type="submit"><slot /></button>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
103
|
+
|
|
52
104
|
```json
|
|
53
105
|
[
|
|
54
106
|
{
|
|
55
|
-
"selector": "
|
|
56
|
-
"as":
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
107
|
+
"selector": "SubmitButton",
|
|
108
|
+
"as": {
|
|
109
|
+
"element": "button",
|
|
110
|
+
"attrs": [{ "name": "type", "value": "submit" }],
|
|
111
|
+
"slots": true
|
|
112
|
+
}
|
|
61
113
|
}
|
|
62
114
|
]
|
|
63
115
|
```
|
|
64
116
|
|
|
117
|
+
Slot detection covers:
|
|
118
|
+
|
|
119
|
+
- `<slot>` elements in Vue, Svelte, and Astro
|
|
120
|
+
- `{@render children()}` snippets in Svelte 5
|
|
121
|
+
|
|
122
|
+
### Import Resolver
|
|
123
|
+
|
|
124
|
+
The import resolver analyzes `<script>` / frontmatter / ESM blocks in component files and extracts import bindings. This links template component usage to source file locations, enabling cross-file dependency resolution.
|
|
125
|
+
|
|
126
|
+
Supported script block types:
|
|
127
|
+
|
|
128
|
+
- Vue `<script setup>` (all static imports are exposed as bindings)
|
|
129
|
+
- Vue Options API `<script>` (fallback when no `<script setup>`; only imports registered in `components: { ... }` are returned)
|
|
130
|
+
- Svelte `<script>` (prefers instance script over module script)
|
|
131
|
+
- Astro frontmatter (`---...---`)
|
|
132
|
+
- MDX top-level ESM
|
|
133
|
+
|
|
134
|
+
Dynamic imports with string literal specifiers (`import('./path')`) are included in bindings with `type: 'dynamic'`. Template literal and variable specifiers are excluded.
|
|
135
|
+
|
|
136
|
+
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
|
+
|
|
65
138
|
## API
|
|
66
139
|
|
|
140
|
+
### `scan(files, options)`
|
|
141
|
+
|
|
142
|
+
The unified entry point. Dispatches files to the appropriate scanner based on file extension, runs both scanners in parallel, and returns the merged, sorted results.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { scan } from '@markuplint/pretenders';
|
|
146
|
+
|
|
147
|
+
const pretenders = await scan([
|
|
148
|
+
'/absolute/path/to/Button.tsx',
|
|
149
|
+
'/absolute/path/to/Card.vue',
|
|
150
|
+
'/absolute/path/to/Alert.svelte',
|
|
151
|
+
]);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Parameters
|
|
155
|
+
|
|
156
|
+
| Parameter | Type | Description |
|
|
157
|
+
| ------------------------------ | ------------------- | --------------------------------------- |
|
|
158
|
+
| `files` | `readonly string[]` | Absolute file paths to scan |
|
|
159
|
+
| `options.ignoreComponentNames` | `readonly string[]` | Component names to exclude from results |
|
|
160
|
+
|
|
67
161
|
### `jsxScanner(files, options)`
|
|
68
162
|
|
|
163
|
+
Scans JSX/TSX files using the TypeScript compiler API.
|
|
164
|
+
|
|
69
165
|
```ts
|
|
70
166
|
import { jsxScanner } from '@markuplint/pretenders';
|
|
71
167
|
|
|
72
|
-
const pretenders = jsxScanner(['
|
|
168
|
+
const pretenders = await jsxScanner(['/absolute/path/to/Component.jsx'], {
|
|
73
169
|
cwd: process.cwd(),
|
|
74
170
|
asFragment: [/(?:^|\.)provider$/i],
|
|
75
171
|
ignoreComponentNames: [],
|
|
76
|
-
taggedStylingComponent: [
|
|
77
|
-
// PropertyAccessExpression: styled.button`css-prop: value;`
|
|
78
|
-
/^styled\.(?<tagName>[a-z][\da-z]*)$/i,
|
|
79
|
-
// CallExpression: styled(Button)`css-prop: value;`
|
|
80
|
-
/^styled\s*\(\s*(?<tagName>[a-z][\da-z]*)\s*\)$/i,
|
|
81
|
-
],
|
|
172
|
+
taggedStylingComponent: [/^styled\.(?<tagName>[a-z][\da-z]*)$/i, /^styled\s*\(\s*(?<tagName>[a-z][\da-z]*)\s*\)$/i],
|
|
82
173
|
extendingWrapper: [],
|
|
83
174
|
});
|
|
84
175
|
```
|
|
85
176
|
|
|
86
|
-
####
|
|
177
|
+
#### Parameters
|
|
87
178
|
|
|
88
|
-
Type
|
|
179
|
+
| Parameter | Type | Description |
|
|
180
|
+
| -------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
181
|
+
| `files` | `readonly string[]` | Absolute file paths to scan |
|
|
182
|
+
| `options.cwd` | `string` | Current working directory |
|
|
183
|
+
| `options.asFragment` | `readonly (RegExp \| string)[]` | Patterns for components treated as transparent fragments |
|
|
184
|
+
| `options.ignoreComponentNames` | `readonly string[]` | Component names to ignore |
|
|
185
|
+
| `options.taggedStylingComponent` | `readonly (RegExp \| string)[]` | Patterns for styled-components tagged template expressions |
|
|
186
|
+
| `options.extendingWrapper` | `readonly (string \| RegExp \| ExtendingWrapperCallerOptions)[]` | Patterns for HOC/wrapper components |
|
|
89
187
|
|
|
90
|
-
|
|
188
|
+
### `templateScanner(files, options)`
|
|
91
189
|
|
|
92
|
-
|
|
190
|
+
Scans Vue, Svelte, and Astro component files using markuplint's own parsers (MLAST-based).
|
|
93
191
|
|
|
94
|
-
|
|
192
|
+
```ts
|
|
193
|
+
import { templateScanner } from '@markuplint/pretenders';
|
|
95
194
|
|
|
96
|
-
|
|
195
|
+
const pretenders = await templateScanner(
|
|
196
|
+
['/absolute/path/to/Button.vue', '/absolute/path/to/Alert.svelte', '/absolute/path/to/Card.astro'],
|
|
197
|
+
{
|
|
198
|
+
ignoreComponentNames: ['InternalHelper'],
|
|
199
|
+
},
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Parameters
|
|
97
204
|
|
|
98
|
-
|
|
205
|
+
| Parameter | Type | Description |
|
|
206
|
+
| ------------------------------ | ------------------- | ---------------------------------------------- |
|
|
207
|
+
| `files` | `readonly string[]` | Absolute file paths to scan |
|
|
208
|
+
| `options.cwd` | `string` | Current working directory (for relative paths) |
|
|
209
|
+
| `options.ignoreComponentNames` | `readonly string[]` | Component names to exclude from results |
|
|
99
210
|
|
|
100
|
-
|
|
211
|
+
### `analyzeImports(filePath, source)`
|
|
101
212
|
|
|
102
|
-
|
|
213
|
+
Extracts import bindings from a component file's script block. Detects the framework from the file extension and extracts the appropriate source block automatically.
|
|
103
214
|
|
|
104
|
-
|
|
215
|
+
Returns `null` if the file extension is not a supported framework (`.vue`, `.svelte`, `.astro`, `.mdx`).
|
|
105
216
|
|
|
106
|
-
|
|
217
|
+
```ts
|
|
218
|
+
import { analyzeImports } from '@markuplint/pretenders';
|
|
219
|
+
|
|
220
|
+
const result = await analyzeImports('App.vue', source);
|
|
221
|
+
// result.bindings: [{ localName: 'MyButton', importedName: 'default', source: './components/MyButton.vue', type: 'default' }, ...]
|
|
222
|
+
```
|
|
107
223
|
|
|
108
|
-
|
|
224
|
+
#### Parameters
|
|
109
225
|
|
|
110
|
-
|
|
226
|
+
| Parameter | Type | Description |
|
|
227
|
+
| ---------- | -------- | ----------------------------------------------------- |
|
|
228
|
+
| `filePath` | `string` | File path (used for framework detection by extension) |
|
|
229
|
+
| `source` | `string` | Full source text of the component file |
|
|
111
230
|
|
|
112
|
-
|
|
231
|
+
#### Returns
|
|
113
232
|
|
|
114
|
-
|
|
233
|
+
`Promise<ImportAnalysisResult | null>` — The analysis result with all import bindings, or `null` if the framework is not supported.
|
|
115
234
|
|
|
116
|
-
|
|
235
|
+
### `resolveComponentImport(componentName, bindings)`
|
|
117
236
|
|
|
118
|
-
|
|
237
|
+
Resolves a component name used in a template to its import binding. Handles Vue's kebab-case to PascalCase normalization (e.g., `<my-button>` resolves to `MyButton`).
|
|
119
238
|
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
numberOfArgument: 1,
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
});
|
|
239
|
+
```ts
|
|
240
|
+
import { resolveComponentImport } from '@markuplint/pretenders';
|
|
241
|
+
|
|
242
|
+
const binding = resolveComponentImport('my-button', bindings);
|
|
243
|
+
// binding: { localName: 'MyButton', importedName: 'default', source: './components/MyButton.vue', type: 'default' }
|
|
129
244
|
```
|
|
130
245
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
246
|
+
#### Parameters
|
|
247
|
+
|
|
248
|
+
| Parameter | Type | Description |
|
|
249
|
+
| --------------- | -------------------------- | -------------------------------------- |
|
|
250
|
+
| `componentName` | `string` | Component name as used in the template |
|
|
251
|
+
| `bindings` | `readonly ImportBinding[]` | Import bindings from `analyzeImports` |
|
|
252
|
+
|
|
253
|
+
#### Returns
|
|
254
|
+
|
|
255
|
+
`ImportBinding | undefined` — The matching binding, or `undefined` if no match.
|
|
256
|
+
|
|
257
|
+
### `resolveBarrelExport(specifier, importedName, importerPath)`
|
|
258
|
+
|
|
259
|
+
Resolves a barrel file (`index.ts`/`index.js`) re-export to the original source module path. Only handles relative specifiers and single-level barrel resolution.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import { analyzeImports, resolveComponentImport, resolveBarrelExport } from '@markuplint/pretenders';
|
|
263
|
+
|
|
264
|
+
const result = await analyzeImports('App.vue', source);
|
|
265
|
+
const binding = resolveComponentImport('Button', result.bindings);
|
|
266
|
+
|
|
267
|
+
if (binding) {
|
|
268
|
+
const originalSource = resolveBarrelExport(binding.source, binding.importedName, '/absolute/path/to/App.vue');
|
|
269
|
+
// originalSource: './Button.vue' (resolved from './components' barrel)
|
|
270
|
+
}
|
|
134
271
|
```
|
|
135
272
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
273
|
+
#### Parameters
|
|
274
|
+
|
|
275
|
+
| Parameter | Type | Description |
|
|
276
|
+
| -------------- | -------- | ----------------------------------------------- |
|
|
277
|
+
| `specifier` | `string` | The import specifier (e.g., `'./components'`) |
|
|
278
|
+
| `importedName` | `string` | The name being imported (e.g., `'Button'`) |
|
|
279
|
+
| `importerPath` | `string` | Absolute path of the file containing the import |
|
|
280
|
+
|
|
281
|
+
#### Returns
|
|
282
|
+
|
|
283
|
+
`string | null` — The relative source path from the barrel file, or `null` if not a barrel or name not found.
|
package/lib/cli.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import meow from 'meow';
|
|
13
13
|
import { getFileList } from './input.js';
|
|
14
|
-
import { jsxScanner } from './jsx/index.js';
|
|
15
14
|
import { out } from './out.js';
|
|
15
|
+
import { scan } from './scan.js';
|
|
16
16
|
const commands = meow({
|
|
17
17
|
importMeta: import.meta,
|
|
18
18
|
flags: {
|
|
@@ -31,8 +31,7 @@ if (commands.input.length === 0) {
|
|
|
31
31
|
}
|
|
32
32
|
async function main() {
|
|
33
33
|
const files = await getFileList(commands.input);
|
|
34
|
-
const
|
|
35
|
-
const pretenders = await jsxScanner(jsxFiles, {
|
|
34
|
+
const pretenders = await scan(files, {
|
|
36
35
|
ignoreComponentNames: commands.flags.ignore?.split(',').map(s => s.trim()),
|
|
37
36
|
});
|
|
38
37
|
const outFilePath = path.resolve(process.cwd(), commands.flags.out);
|
|
@@ -1,42 +1,25 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PretenderDirectorMap } from './pretender-director.js';
|
|
2
|
+
import type { Identifier } from './types.js';
|
|
2
3
|
import type { Pretender } from '@markuplint/ml-config';
|
|
3
|
-
/**
|
|
4
|
-
* Internal map structure storing component identifiers to their identity and source location.
|
|
5
|
-
*/
|
|
6
|
-
type PretenderDirectorMap = Map<Identifier, [identity: Identity, filePath?: string]>;
|
|
7
|
-
/**
|
|
8
|
-
* Collects and manages pretender mappings discovered during source file scanning.
|
|
9
|
-
* Acts as a registry where component-to-element relationships are added during
|
|
10
|
-
* traversal, then resolved into a flat list of pretenders with dependency linking.
|
|
11
|
-
*/
|
|
12
|
-
export declare class PretenderDirector {
|
|
13
|
-
#private;
|
|
14
|
-
/**
|
|
15
|
-
* Registers a component as a pretender mapping. If the identifier is already
|
|
16
|
-
* registered, the call is silently ignored (first definition wins).
|
|
17
|
-
*
|
|
18
|
-
* @param identifier - The component selector (e.g., component name)
|
|
19
|
-
* @param identity - The native HTML element the component renders as
|
|
20
|
-
* @param filePath - The relative file path where the component is defined
|
|
21
|
-
* @param line - The line number of the component declaration
|
|
22
|
-
* @param col - The column number of the component declaration
|
|
23
|
-
*/
|
|
24
|
-
add(identifier: Identifier, identity: Identity, filePath: string, line: number, col: number): void;
|
|
25
|
-
/**
|
|
26
|
-
* Resolves all registered mappings into a sorted array of Pretender objects.
|
|
27
|
-
* Follows component-to-component chains to determine the final native element identity.
|
|
28
|
-
*
|
|
29
|
-
* @returns A sorted array of resolved Pretender objects
|
|
30
|
-
*/
|
|
31
|
-
getPretenders(): Pretender[];
|
|
32
|
-
}
|
|
33
4
|
/**
|
|
34
5
|
* Resolves a map of component-to-identity mappings into a flat array of Pretender objects.
|
|
35
6
|
* Follows chains where one component wraps another (e.g., MyButton -> Button -> button)
|
|
36
|
-
* until a native element is reached or a
|
|
7
|
+
* until a native element is reached or a cycle is detected.
|
|
37
8
|
*
|
|
38
|
-
*
|
|
9
|
+
* Uses import-path-based resolution when a name index is provided, falling back to
|
|
10
|
+
* name-based lookup for backward compatibility.
|
|
11
|
+
*
|
|
12
|
+
* @param map - The map of component keys to their [identifier, identity, filePath] tuples
|
|
13
|
+
* @param nameIndex - Optional mapping from component names to map keys for resolving references
|
|
39
14
|
* @returns A sorted array of fully resolved Pretender objects
|
|
40
15
|
*/
|
|
41
|
-
export declare function dependencyMapper(map: Readonly<PretenderDirectorMap
|
|
42
|
-
|
|
16
|
+
export declare function dependencyMapper(map: Readonly<PretenderDirectorMap>, nameIndex?: Readonly<Map<Identifier, string>>): Pretender[];
|
|
17
|
+
/**
|
|
18
|
+
* Creates a comparator function that sorts objects by a specified property.
|
|
19
|
+
*
|
|
20
|
+
* @template T - The object type
|
|
21
|
+
* @template P - The property key type
|
|
22
|
+
* @param propName - The property to sort by (case-insensitive for strings)
|
|
23
|
+
* @returns A comparator function for use with `Array.prototype.sort()`
|
|
24
|
+
*/
|
|
25
|
+
export declare function propSort<T, P extends keyof T>(propName: P): (a: T, b: T) => 1 | -1 | 0;
|
package/lib/dependency-mapper.js
CHANGED
|
@@ -1,89 +1,80 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collects and manages pretender mappings discovered during source file scanning.
|
|
3
|
-
* Acts as a registry where component-to-element relationships are added during
|
|
4
|
-
* traversal, then resolved into a flat list of pretenders with dependency linking.
|
|
5
|
-
*/
|
|
6
|
-
export class PretenderDirector {
|
|
7
|
-
#map = new Map();
|
|
8
|
-
/**
|
|
9
|
-
* Registers a component as a pretender mapping. If the identifier is already
|
|
10
|
-
* registered, the call is silently ignored (first definition wins).
|
|
11
|
-
*
|
|
12
|
-
* @param identifier - The component selector (e.g., component name)
|
|
13
|
-
* @param identity - The native HTML element the component renders as
|
|
14
|
-
* @param filePath - The relative file path where the component is defined
|
|
15
|
-
* @param line - The line number of the component declaration
|
|
16
|
-
* @param col - The column number of the component declaration
|
|
17
|
-
*/
|
|
18
|
-
add(identifier, identity, filePath, line, col) {
|
|
19
|
-
if (this.#map.has(identifier)) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
this.#map.set(identifier, [identity, `${filePath}:${line}:${col}`]);
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Resolves all registered mappings into a sorted array of Pretender objects.
|
|
26
|
-
* Follows component-to-component chains to determine the final native element identity.
|
|
27
|
-
*
|
|
28
|
-
* @returns A sorted array of resolved Pretender objects
|
|
29
|
-
*/
|
|
30
|
-
getPretenders() {
|
|
31
|
-
return dependencyMapper(this.#map);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
1
|
/**
|
|
35
2
|
* Resolves a map of component-to-identity mappings into a flat array of Pretender objects.
|
|
36
3
|
* Follows chains where one component wraps another (e.g., MyButton -> Button -> button)
|
|
37
|
-
* until a native element is reached or a
|
|
4
|
+
* until a native element is reached or a cycle is detected.
|
|
38
5
|
*
|
|
39
|
-
*
|
|
6
|
+
* Uses import-path-based resolution when a name index is provided, falling back to
|
|
7
|
+
* name-based lookup for backward compatibility.
|
|
8
|
+
*
|
|
9
|
+
* @param map - The map of component keys to their [identifier, identity, filePath] tuples
|
|
10
|
+
* @param nameIndex - Optional mapping from component names to map keys for resolving references
|
|
40
11
|
* @returns A sorted array of fully resolved Pretender objects
|
|
41
12
|
*/
|
|
42
|
-
export function dependencyMapper(map) {
|
|
13
|
+
export function dependencyMapper(map, nameIndex) {
|
|
14
|
+
const resolvedNameIndex = nameIndex ?? buildNameIndex(map);
|
|
43
15
|
const linkedPretenders = [];
|
|
44
|
-
const
|
|
45
|
-
for (const [identifier, [_identity, _filePath]] of collection) {
|
|
16
|
+
for (const [key, [identifier, _identity, _filePath]] of map) {
|
|
46
17
|
let identity = _identity;
|
|
47
18
|
let filePath = _filePath;
|
|
48
19
|
let elName = getElName(identity);
|
|
49
20
|
const via = [];
|
|
21
|
+
const visited = new Set([key]);
|
|
50
22
|
while (true) {
|
|
51
|
-
const
|
|
23
|
+
const lookupKey = resolvedNameIndex.get(elName) ?? elName;
|
|
24
|
+
const mappedPretender = map.get(lookupKey);
|
|
52
25
|
if (!mappedPretender) {
|
|
53
26
|
break;
|
|
54
27
|
}
|
|
55
|
-
identity = mappedPretender[
|
|
56
|
-
filePath = mappedPretender[
|
|
57
|
-
if (
|
|
28
|
+
identity = mappedPretender[1];
|
|
29
|
+
filePath = mappedPretender[2];
|
|
30
|
+
if (visited.has(lookupKey)) {
|
|
58
31
|
via.push('...[Recursive]');
|
|
59
32
|
break;
|
|
60
33
|
}
|
|
34
|
+
visited.add(lookupKey);
|
|
61
35
|
via.push(elName);
|
|
62
36
|
elName = getElName(identity);
|
|
63
37
|
}
|
|
64
38
|
const pretender = {
|
|
65
39
|
selector: identifier,
|
|
66
40
|
as: identity,
|
|
41
|
+
...(filePath ? { filePath } : {}),
|
|
67
42
|
};
|
|
68
|
-
if (filePath) {
|
|
69
|
-
// @ts-ignore initialize readonly property
|
|
70
|
-
pretender.filePath = filePath;
|
|
71
|
-
}
|
|
72
43
|
if (via.length > 0) {
|
|
73
|
-
|
|
74
|
-
pretender._via = via;
|
|
44
|
+
Object.assign(pretender, { _via: via });
|
|
75
45
|
}
|
|
76
46
|
linkedPretenders.push(pretender);
|
|
77
47
|
}
|
|
78
48
|
return linkedPretenders.toSorted(propSort('selector'));
|
|
79
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Builds a name-to-key index from the map for backward-compatible name-based lookup.
|
|
52
|
+
* First definition wins when multiple entries share the same identifier.
|
|
53
|
+
*/
|
|
54
|
+
function buildNameIndex(map) {
|
|
55
|
+
const index = new Map();
|
|
56
|
+
for (const [key, [identifier]] of map) {
|
|
57
|
+
if (!index.has(identifier)) {
|
|
58
|
+
index.set(identifier, key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return index;
|
|
62
|
+
}
|
|
80
63
|
function getElName(identity) {
|
|
81
64
|
if (typeof identity === 'string') {
|
|
82
65
|
return identity;
|
|
83
66
|
}
|
|
84
67
|
return identity.element;
|
|
85
68
|
}
|
|
86
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Creates a comparator function that sorts objects by a specified property.
|
|
71
|
+
*
|
|
72
|
+
* @template T - The object type
|
|
73
|
+
* @template P - The property key type
|
|
74
|
+
* @param propName - The property to sort by (case-insensitive for strings)
|
|
75
|
+
* @returns A comparator function for use with `Array.prototype.sort()`
|
|
76
|
+
*/
|
|
77
|
+
export function propSort(propName) {
|
|
87
78
|
return (a, b) => {
|
|
88
79
|
const nameA = toLowerCase(a[propName]);
|
|
89
80
|
const nameB = toLowerCase(b[propName]);
|
|
@@ -98,7 +89,6 @@ function propSort(propName) {
|
|
|
98
89
|
}
|
|
99
90
|
function toLowerCase(value) {
|
|
100
91
|
if (typeof value === 'string') {
|
|
101
|
-
// @ts-ignore
|
|
102
92
|
return value.toLowerCase();
|
|
103
93
|
}
|
|
104
94
|
return value;
|