@groma/scanner-vue 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/dist/index.js +62 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,15 @@ configuration, SFC parsing, template code generation and original-source
|
|
|
30
30
|
mappings. [Volar](https://github.com/volarjs/volar.js) integrates the generated
|
|
31
31
|
code with the TypeScript checker, which resolves component imports and handler
|
|
32
32
|
function identities. The scanner translates these facts into the existing
|
|
33
|
-
supplied-callback evidence contract; it has no separate
|
|
33
|
+
supplied-callback evidence contract; it has no separate type resolver, and its
|
|
34
|
+
only own name lookup is a template tag that no import names, on Vue's
|
|
35
|
+
`GlobalComponents`.
|
|
36
|
+
|
|
37
|
+
A root `tsconfig.json` that names no source and references other configs, as
|
|
38
|
+
in Nuxt 4, compiles a package through the first existing referenced config that
|
|
39
|
+
includes one of its SFCs, with that config's declaration files even when Git
|
|
40
|
+
ignores them. Before the framework generates those configs, the root config's
|
|
41
|
+
own settings apply.
|
|
34
42
|
|
|
35
43
|
The package declares the default
|
|
36
44
|
[include and exclude lists](../index.md#selecting-source-files). It includes
|
|
@@ -65,7 +73,13 @@ trigger the existing shared scanner refresh.
|
|
|
65
73
|
### Inline event bindings
|
|
66
74
|
|
|
67
75
|
The qualified example is Vue REPL's CodeMirror `change` event bound to the
|
|
68
|
-
parent's `onChange` function.
|
|
76
|
+
parent's `onChange` function. The child is a static imported SFC or, when no
|
|
77
|
+
import names the tag, an SFC registered for every template on Vue's
|
|
78
|
+
`GlobalComponents` as `Name: typeof import('./Name.vue')['default']`, the form
|
|
79
|
+
Nuxt and unplugin-vue-components generate. An import shadows a global
|
|
80
|
+
registration of the same name, and `<game-tile>` finds `GameTile`. Global
|
|
81
|
+
registrations need Vue installed and the generated declarations present, as
|
|
82
|
+
after `nuxt prepare`. The child must assign `defineEmits`
|
|
69
83
|
to a constant and declare the event in a type literal, function type, or
|
|
70
84
|
literal runtime array. A call to that same symbol must supply a declared
|
|
71
85
|
literal event name inside a source function or directly in an inline template
|
package/dist/index.js
CHANGED
|
@@ -40037,6 +40037,32 @@ function projectConfig(root, repositoryRoot, files) {
|
|
|
40037
40037
|
throw new Error(`${root}: no TypeScript config in the package or its repository ancestors`);
|
|
40038
40038
|
}
|
|
40039
40039
|
}
|
|
40040
|
+
function readConfig(configFile) {
|
|
40041
|
+
const vue = import_language_core.createParsedCommandLine(vueTypeScript, ts.sys, configFile);
|
|
40042
|
+
const plugin = import_language_core.createVueLanguagePlugin(vueTypeScript, vue.options, vue.vueOptions, (id) => id);
|
|
40043
|
+
const parsed = ts.parseJsonSourceFileConfigFileContent(ts.readJsonConfigFile(configFile, ts.sys.readFile), ts.sys, path2.dirname(configFile), {}, configFile, undefined, plugin.typescript.extraFileExtensions);
|
|
40044
|
+
return { configFile, vueOptions: vue.vueOptions, plugin, parsed };
|
|
40045
|
+
}
|
|
40046
|
+
function sourceConfig(configFile, packageSfc) {
|
|
40047
|
+
const config = readConfig(configFile);
|
|
40048
|
+
if (config.parsed.fileNames.length)
|
|
40049
|
+
return config;
|
|
40050
|
+
for (const reference of config.parsed.projectReferences ?? []) {
|
|
40051
|
+
const referencedFile = ts.resolveProjectReferencePath(reference);
|
|
40052
|
+
if (!ts.sys.fileExists(referencedFile))
|
|
40053
|
+
continue;
|
|
40054
|
+
const referenced = readConfig(referencedFile);
|
|
40055
|
+
if (referenced.parsed.fileNames.some(packageSfc))
|
|
40056
|
+
return referenced;
|
|
40057
|
+
}
|
|
40058
|
+
return config;
|
|
40059
|
+
}
|
|
40060
|
+
function defaultExport(index) {
|
|
40061
|
+
return ts.isLiteralTypeNode(index) && ts.isStringLiteral(index.literal) && index.literal.text === "default";
|
|
40062
|
+
}
|
|
40063
|
+
function pascalCase(tag) {
|
|
40064
|
+
return tag.replace(/-(\w)/g, (_, letter) => letter.toUpperCase()).replace(/^\w/, (letter) => letter.toUpperCase());
|
|
40065
|
+
}
|
|
40040
40066
|
|
|
40041
40067
|
class VueProject {
|
|
40042
40068
|
program;
|
|
@@ -40045,6 +40071,7 @@ class VueProject {
|
|
|
40045
40071
|
root;
|
|
40046
40072
|
options;
|
|
40047
40073
|
diagnostics;
|
|
40074
|
+
globals;
|
|
40048
40075
|
language;
|
|
40049
40076
|
plugin;
|
|
40050
40077
|
readable;
|
|
@@ -40056,13 +40083,9 @@ class VueProject {
|
|
|
40056
40083
|
const local = relative(repositoryRoot, file);
|
|
40057
40084
|
return (owners.get(local) ?? root) === root && (local.startsWith("../") || files.has(local));
|
|
40058
40085
|
};
|
|
40059
|
-
const configFile = projectConfig(root, repositoryRoot, files);
|
|
40060
|
-
|
|
40061
|
-
|
|
40062
|
-
const vue = import_language_core.createParsedCommandLine(vueTypeScript, ts.sys, configFile);
|
|
40063
|
-
this.options = vue.vueOptions;
|
|
40064
|
-
this.plugin = import_language_core.createVueLanguagePlugin(vueTypeScript, vue.options, vue.vueOptions, (id) => id);
|
|
40065
|
-
const parsed = ts.parseJsonSourceFileConfigFileContent(config, ts.sys, configRoot, {}, configFile, undefined, this.plugin.typescript.extraFileExtensions);
|
|
40086
|
+
const { configFile, vueOptions, plugin, parsed } = sourceConfig(projectConfig(root, repositoryRoot, files), (file) => file.endsWith(".vue") && selected(file));
|
|
40087
|
+
this.options = vueOptions;
|
|
40088
|
+
this.plugin = plugin;
|
|
40066
40089
|
failDiagnostics(parsed.errors.filter((item) => item.code !== 5083));
|
|
40067
40090
|
this.diagnostics = parsed.errors.filter((item) => item.code === 5083).map((item) => ({
|
|
40068
40091
|
severity: "warning",
|
|
@@ -40079,7 +40102,7 @@ class VueProject {
|
|
|
40079
40102
|
this.language = language;
|
|
40080
40103
|
}
|
|
40081
40104
|
}))({
|
|
40082
|
-
rootNames: [...new Set([...assigned, ...parsed.fileNames.filter((file) => selected(file) || !ts.sys.fileExists(file))])],
|
|
40105
|
+
rootNames: [...new Set([...assigned, ...parsed.fileNames.filter((file) => selected(file) || !ts.sys.fileExists(file) || /\.d\.[cm]?ts$/.test(file))])],
|
|
40083
40106
|
options: parsed.options,
|
|
40084
40107
|
host
|
|
40085
40108
|
});
|
|
@@ -40091,6 +40114,11 @@ class VueProject {
|
|
|
40091
40114
|
message: `A declared TypeScript source is absent; available source was still scanned. ${ts.flattenDiagnosticMessageText(item.messageText, " ")}`
|
|
40092
40115
|
})));
|
|
40093
40116
|
this.checker = this.program.getTypeChecker();
|
|
40117
|
+
const vueResolution = ts.resolveModuleName("vue", path2.join(root, "package.json"), parsed.options, host).resolvedModule;
|
|
40118
|
+
const vueSource = vueResolution && this.program.getSourceFile(vueResolution.resolvedFileName);
|
|
40119
|
+
const vueModule = vueSource && this.checker.getSymbolAtLocation(vueSource);
|
|
40120
|
+
const exported = vueModule && this.checker.getExportsOfModule(vueModule).find((symbol) => symbol.name === "GlobalComponents");
|
|
40121
|
+
this.globals = exported && this.checker.getDeclaredTypeOfSymbol(exported);
|
|
40094
40122
|
this.files = this.program.getSourceFiles().filter((source) => this.owned(source) && selected(source.fileName));
|
|
40095
40123
|
for (const source of this.files)
|
|
40096
40124
|
this.validateSfc(source.fileName);
|
|
@@ -40098,6 +40126,16 @@ class VueProject {
|
|
|
40098
40126
|
owned(source) {
|
|
40099
40127
|
return !source.isDeclarationFile && !relative(this.root, source.fileName).startsWith("../") && !source.fileName.includes("/node_modules/") && this.readable.has(relative(this.root, source.fileName));
|
|
40100
40128
|
}
|
|
40129
|
+
globalComponents(tag) {
|
|
40130
|
+
return (this.globals?.getProperty(pascalCase(tag))?.declarations ?? []).flatMap((declaration) => {
|
|
40131
|
+
const type = ts.isPropertySignature(declaration) ? declaration.type : undefined;
|
|
40132
|
+
if (!type || !ts.isIndexedAccessTypeNode(type) || !ts.isImportTypeNode(type.objectType) || !defaultExport(type.indexType))
|
|
40133
|
+
return [];
|
|
40134
|
+
const source = this.checker.getTypeFromTypeNode(type.objectType).getSymbol()?.valueDeclaration;
|
|
40135
|
+
const sfc = source && ts.isSourceFile(source) && this.owned(source) && this.sfc(source.fileName);
|
|
40136
|
+
return sfc ? [sfc] : [];
|
|
40137
|
+
});
|
|
40138
|
+
}
|
|
40101
40139
|
sfc(file) {
|
|
40102
40140
|
const code = this.language.scripts.get(file)?.generated?.root;
|
|
40103
40141
|
return code instanceof import_language_core.VueVirtualCode ? code : undefined;
|
|
@@ -40238,24 +40276,23 @@ class VueEvidence {
|
|
|
40238
40276
|
}
|
|
40239
40277
|
}
|
|
40240
40278
|
}
|
|
40241
|
-
component(file, position) {
|
|
40242
|
-
const candidates = new Set;
|
|
40243
|
-
for (const node of this.project.nodes(file, position)) {
|
|
40244
|
-
if (!ts2.isIdentifier(node))
|
|
40245
|
-
continue;
|
|
40246
|
-
const symbol = this.project.checker.getSymbolAtLocation(node);
|
|
40247
|
-
if (!symbol || !(symbol.flags & ts2.SymbolFlags.Alias))
|
|
40248
|
-
continue;
|
|
40249
|
-
const target = this.project.checker.getAliasedSymbol(symbol);
|
|
40250
|
-
for (const declaration of target.declarations ?? []) {
|
|
40251
|
-
const source = declaration.getSourceFile();
|
|
40252
|
-
const sfc = this.project.owned(source) && this.project.sfc(source.fileName);
|
|
40253
|
-
if (sfc)
|
|
40254
|
-
candidates.add(sfc);
|
|
40255
|
-
}
|
|
40256
|
-
}
|
|
40279
|
+
component(file, element, position) {
|
|
40280
|
+
const candidates = new Set(this.importedComponents(file, position) ?? this.project.globalComponents(element.tag));
|
|
40257
40281
|
return candidates.size === 1 ? [...candidates][0] : undefined;
|
|
40258
40282
|
}
|
|
40283
|
+
importedComponents(file, position) {
|
|
40284
|
+
const aliases = this.project.nodes(file, position).flatMap((node) => {
|
|
40285
|
+
const symbol = ts2.isIdentifier(node) ? this.project.checker.getSymbolAtLocation(node) : undefined;
|
|
40286
|
+
return symbol && symbol.flags & ts2.SymbolFlags.Alias ? [this.project.checker.getAliasedSymbol(symbol)] : [];
|
|
40287
|
+
});
|
|
40288
|
+
if (!aliases.length)
|
|
40289
|
+
return;
|
|
40290
|
+
return aliases.flatMap((target) => (target.declarations ?? []).flatMap((declaration) => {
|
|
40291
|
+
const source = declaration.getSourceFile();
|
|
40292
|
+
const sfc = this.project.owned(source) && this.project.sfc(source.fileName);
|
|
40293
|
+
return sfc ? [sfc] : [];
|
|
40294
|
+
}));
|
|
40295
|
+
}
|
|
40259
40296
|
handler(file, position) {
|
|
40260
40297
|
const candidates = new Set;
|
|
40261
40298
|
for (const node of this.project.nodes(file, position)) {
|
|
@@ -40380,7 +40417,7 @@ class VueEvidence {
|
|
|
40380
40417
|
const statement = expression.statements[0];
|
|
40381
40418
|
if (expression.statements.length !== 1 || !statement || !ts2.isExpressionStatement(statement) || !ts2.isIdentifier(statement.expression))
|
|
40382
40419
|
return false;
|
|
40383
|
-
const child = this.component(file, templateOffset + element.loc.start.offset + 1);
|
|
40420
|
+
const child = this.component(file, element, templateOffset + element.loc.start.offset + 1);
|
|
40384
40421
|
const target = this.handler(file, templateOffset + event.exp.loc.start.offset);
|
|
40385
40422
|
if (!child || !target)
|
|
40386
40423
|
return false;
|