@markuplint/pretenders 5.0.0-dev.5 → 5.0.0-rc.1
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 +29 -0
- package/README.md +207 -62
- package/lib/cli.js +2 -3
- package/lib/component-scanner.d.ts +67 -0
- package/lib/component-scanner.js +9 -0
- package/lib/dependency-mapper.d.ts +18 -35
- package/lib/dependency-mapper.js +40 -50
- package/lib/import-resolver/extract-script-source.d.ts +59 -0
- package/lib/import-resolver/extract-script-source.js +143 -0
- package/lib/import-resolver/index.d.ts +55 -0
- package/lib/import-resolver/index.js +152 -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 +27 -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 +14 -5
- package/lib/pretender-director.js +15 -6
- package/lib/scan.d.ts +20 -0
- package/lib/scan.js +24 -0
- package/lib/scanner-loader.d.ts +8 -0
- package/lib/scanner-loader.js +56 -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 +57 -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 +12 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
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.1](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.0...v5.0.0-rc.1) (2026-03-27)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @markuplint/pretenders
|
|
9
|
+
|
|
10
|
+
# [5.0.0-rc.0](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.3...v5.0.0-rc.0) (2026-03-12)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **pretenders,ml-config:** add scan field to JSON schema and narrow getParser error handling ([e5fda17](https://github.com/markuplint/markuplint/commit/e5fda171f886c1b65d6f0e536c182cebb279ea07))
|
|
15
|
+
- **pretenders:** add error handling to parseComponent and resolveBarrelExport ([c203c4b](https://github.com/markuplint/markuplint/commit/c203c4be341c89abfd0ba7991f09d8322d17728e))
|
|
16
|
+
- **pretenders:** address QA review findings for import resolver phase 2 ([d8fd73f](https://github.com/markuplint/markuplint/commit/d8fd73f533e9b1febec05d93f144f2d5122cffc9))
|
|
17
|
+
- **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)
|
|
18
|
+
- **pretenders:** fix ensureInit TOCTOU race condition ([d9643d8](https://github.com/markuplint/markuplint/commit/d9643d8b679e888b143d9d47f243e35fbeeb6752))
|
|
19
|
+
- **pretenders:** fix false positives in children detection and harden tests ([96f7af3](https://github.com/markuplint/markuplint/commit/96f7af3adf4d6a3b8c65a9e9464cf1bf34c48613))
|
|
20
|
+
- **pretenders:** guard dynamic parser imports and improve test coverage ([9667aa1](https://github.com/markuplint/markuplint/commit/9667aa1278a3ac737fa8c6cfc2df5c1ffd9834c9))
|
|
21
|
+
- **pretenders:** handle empty path edge case in deriveName ([0613743](https://github.com/markuplint/markuplint/commit/0613743c3a83c124b90a5e3f54c62e23b2a7b3bd))
|
|
22
|
+
- **pretenders:** pass importPath in both scanners to prevent name collision ([8644b21](https://github.com/markuplint/markuplint/commit/8644b21b99f1a5a13ab58a4d6d042d2e1aa2a446))
|
|
23
|
+
- **pretenders:** rename createIndentity to createIdentity ([e8f37e5](https://github.com/markuplint/markuplint/commit/e8f37e52f32107e7cc9a9415d2edb6b3bb442fe5))
|
|
24
|
+
- **pretenders:** warn when parser package is not found ([03d567e](https://github.com/markuplint/markuplint/commit/03d567e937228ecfd5b3d9d7811814efd9dd2d09))
|
|
25
|
+
- 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)
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
- **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)
|
|
30
|
+
- **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)
|
|
31
|
+
- **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)
|
|
32
|
+
- **pretenders:** dispatch CLI input to both JSX and template scanners ([a5535af](https://github.com/markuplint/markuplint/commit/a5535af1e7e496f570cddce52148eb21f9611cfe))
|
|
33
|
+
- **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)
|
|
34
|
+
|
|
6
35
|
# [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
36
|
|
|
8
37
|
**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 delegates to each parser package's `component-scanner` subpath export (e.g., `@markuplint/vue-parser/component-scanner`). Each parser's component-scanner uses its own MLAST parser to extract the root element at depth=0, detect static attributes, slot/children usage, and extract script source blocks. This keeps framework-specific scanning logic co-located with the parser that understands the framework best.
|
|
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
|
+
Script source extraction is delegated to each parser's component-scanner (Vue, Svelte, Astro), while MDX extraction is built-in. Supported script block types:
|
|
127
|
+
|
|
128
|
+
- Vue `<script setup>` (via `@markuplint/vue-parser/component-scanner`)
|
|
129
|
+
- Vue Options API `<script>` (fallback when no `<script setup>`; only imports registered in `components: { ... }` are returned)
|
|
130
|
+
- Svelte `<script>` (via `@markuplint/svelte-parser/component-scanner`; prefers instance script over module script)
|
|
131
|
+
- Astro frontmatter (via `@markuplint/astro-parser/component-scanner`)
|
|
132
|
+
- MDX top-level ESM (built-in)
|
|
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);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module component-scanner
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the Companion Module pattern.
|
|
5
|
+
* Each framework parser package provides its own `component-scanner` subpath
|
|
6
|
+
* implementing these interfaces. The pretenders package owns the types;
|
|
7
|
+
* parser packages import them for implementation.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Result of scanning a single component file for its root element information.
|
|
11
|
+
*/
|
|
12
|
+
export interface ComponentScanResult {
|
|
13
|
+
/** The root element tag name, or `null` if the component is fragment-like */
|
|
14
|
+
readonly rootElement: string | null;
|
|
15
|
+
/** Static attributes on the root element */
|
|
16
|
+
readonly attrs: readonly ComponentScanAttr[];
|
|
17
|
+
/** Whether the component template contains slot usage (Vue `<slot>`, Svelte `{@render}`, etc.) */
|
|
18
|
+
readonly hasSlots: boolean;
|
|
19
|
+
/** Extracted script/ESM source block for import analysis */
|
|
20
|
+
readonly scriptSource?: ComponentScanScriptSource;
|
|
21
|
+
/** SVG namespace indicator (only set when root is in SVG namespace) */
|
|
22
|
+
readonly namespace?: 'svg';
|
|
23
|
+
/** Line number of the root element in the source */
|
|
24
|
+
readonly line?: number;
|
|
25
|
+
/** Column number of the root element in the source */
|
|
26
|
+
readonly col?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A static attribute extracted from a component's root element.
|
|
30
|
+
*/
|
|
31
|
+
export interface ComponentScanAttr {
|
|
32
|
+
/** The attribute name */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/** The attribute value (omitted for boolean attributes) */
|
|
35
|
+
readonly value?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A script/ESM source block extracted from a component file.
|
|
39
|
+
* Used by import-resolver to analyze component imports.
|
|
40
|
+
*/
|
|
41
|
+
export interface ComponentScanScriptSource {
|
|
42
|
+
/** The raw script content without delimiters */
|
|
43
|
+
readonly content: string;
|
|
44
|
+
/** The character offset of the content start within the original source */
|
|
45
|
+
readonly offset: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Interface for framework-specific component scanners.
|
|
49
|
+
* Implemented by each parser package's `component-scanner` subpath export.
|
|
50
|
+
*/
|
|
51
|
+
export interface ComponentScanner {
|
|
52
|
+
/**
|
|
53
|
+
* Scans a single component source file and extracts root element information.
|
|
54
|
+
*
|
|
55
|
+
* @param sourceCode - The full source text of the component file
|
|
56
|
+
* @returns The scan result, or `null` if scanning fails or the file has no root element
|
|
57
|
+
*/
|
|
58
|
+
scanComponent(sourceCode: string): ComponentScanResult | null;
|
|
59
|
+
/**
|
|
60
|
+
* Extracts the script/ESM source block from a component file.
|
|
61
|
+
* Optional — only needed for frameworks that embed scripts (Vue, Svelte, Astro).
|
|
62
|
+
*
|
|
63
|
+
* @param sourceCode - The full source text of the component file
|
|
64
|
+
* @returns The extracted script block, or `null` if none found
|
|
65
|
+
*/
|
|
66
|
+
extractScriptSource?(sourceCode: string): ComponentScanScriptSource | null;
|
|
67
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module component-scanner
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the Companion Module pattern.
|
|
5
|
+
* Each framework parser package provides its own `component-scanner` subpath
|
|
6
|
+
* implementing these interfaces. The pretenders package owns the types;
|
|
7
|
+
* parser packages import them for implementation.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -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 | 0 | -1;
|