Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions graph/test/javascript/cases/69-vue-sfc/src/Comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup>
import { Child, MyBadge } from './child.js';
function sfcHelper() { return 1; }
function onSave(value) { return value; }
const total = 3;
</script>

<template>
<div>
<Child msg="from sfc" @ping="sfcHelper" />
<my-badge v-if="total" />
<button @click="onSave(total)">{{ sfcHelper() }}</button>
</div>
</template>
3 changes: 3 additions & 0 deletions graph/test/javascript/cases/69-vue-sfc/src/TemplateOnly.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<Child @ping="sfcHelper" />
</template>
4 changes: 4 additions & 0 deletions graph/test/javascript/cases/69-vue-sfc/src/Typed.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<script setup lang="ts">
function typedHelper(): number { return 2; }
</script>
<template><button @click="typedHelper">x</button></template>
7 changes: 7 additions & 0 deletions graph/test/javascript/cases/69-vue-sfc/src/child.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function Child(props) {
return props.msg;
}

export function MyBadge() {
return 'badge';
}
5 changes: 5 additions & 0 deletions graph/test/javascript/cases/69-vue-sfc/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Comp from './Comp.vue';

export function mount() {
return Comp;
}
1 change: 1 addition & 0 deletions graph/test/javascript/expected/69-vue-sfc.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package_entry @axiomcode/code-graph . [] MAIN dist/reason.js NOT_STAGED -> -
5 changes: 5 additions & 0 deletions graph/test/javascript/expected/69-vue-sfc.edges
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Comp.vue:10:34 FUNCTION_CALL sfcHelper -> known_edge Comp.vue:3:1 sfcHelper
Comp.vue:10:6 FUNCTION_CALL Child -> known_edge child.js:1:1 Child
Comp.vue:11:6 FUNCTION_CALL MyBadge -> known_edge child.js:5:1 MyBadge
Comp.vue:12:21 FUNCTION_CALL onSave -> known_edge Comp.vue:4:1 onSave
Comp.vue:12:40 FUNCTION_CALL sfcHelper -> known_edge Comp.vue:3:1 sfcHelper
1 change: 1 addition & 0 deletions graph/test/javascript/expected/69-vue-sfc.oracle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# defects: 0
17 changes: 17 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/Comp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import { Child, format } from './child';
function sfcHelper(): number { return 1; }
function onSave(value: number): void { void value; }
const total = 3;
</script>

<template>
<div class="card">
<Child msg="from sfc" @ping="sfcHelper" />
<button @click="onSave(total)">{{ format(total) }}</button>
</div>
</template>

<style scoped>
.card { color: red; }
</style>
4 changes: 4 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/Plain.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<script setup>
function plainHelper() { return 2; }
</script>
<template><button @click="plainHelper">x</button></template>
14 changes: 14 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/TemplateFirst.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<template>
<section>
<my-badge v-if="shown()" />
<input @input="
onInput()
" />
</section>
</template>

<script setup lang="ts">
import { MyBadge } from './child';
function shown(): boolean { return true; }
function onInput(): void {}
</script>
3 changes: 3 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/TemplateOnly.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<Child @ping="sfcHelper" />
</template>
11 changes: 11 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/child.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function Child(props: { msg: string }): string {
return props.msg;
}

export function MyBadge(): string {
return 'badge';
}

export function format(n: number): string {
return n.toFixed(2);
}
5 changes: 5 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Comp from './Comp.vue';

export function mount(): unknown {
return Comp;
}
4 changes: 4 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/shims-vue.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module '*.vue' {
const component: unknown;
export default component;
}
10 changes: 10 additions & 0 deletions graph/test/typescript/cases/80-vue-sfc/src/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true
},
"include": ["**/*.ts", "**/*.vue"]
}
8 changes: 8 additions & 0 deletions graph/test/typescript/expected/80-vue-sfc.edges
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ambiguous_unknown METHOD_CALL child#format(number) @L10 -> -
known_edge FUNCTION_CALL Comp.vue#<module-init>() @L10 -> Comp.vue#sfcHelper()
known_edge FUNCTION_CALL Comp.vue#<module-init>() @L10 -> child#Child({ msg: string })
known_edge FUNCTION_CALL Comp.vue#<module-init>() @L11 -> Comp.vue#onSave(number)
known_edge FUNCTION_CALL Comp.vue#<module-init>() @L11 -> child#format(number)
known_edge FUNCTION_CALL TemplateFirst.vue#<module-init>() @L3 -> TemplateFirst.vue#shown()
known_edge FUNCTION_CALL TemplateFirst.vue#<module-init>() @L3 -> child#MyBadge()
known_edge FUNCTION_CALL TemplateFirst.vue#<module-init>() @L5 -> TemplateFirst.vue#onInput()
5 changes: 5 additions & 0 deletions graph/test/typescript/expected/80-vue-sfc.entries
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
── entry_point (4) ──
exported_from_entry_module main#mount main.ts:3
unimported_module TemplateFirst.vue#<module> TemplateFirst.vue:1
unimported_module main#<module> main.ts:1
unimported_module shims-vue#<module> shims-vue.d.ts:1
1 change: 1 addition & 0 deletions graph/test/typescript/expected/80-vue-sfc.fields
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
known_edge read child#Child({ msg: string }) -> { msg: string }#msg
9 changes: 9 additions & 0 deletions graph/test/typescript/expected/80-vue-sfc.fields-oracle
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
80-vue-sfc [fields]
precision 0.0000 (0 correct, 1 wrong)
recall 0.0000 (0 of 1 the compiler resolved)
sites 1 resolved 1 (100.0%)
tiers known_edge=1
access read=1
not scored: 0 rows whose target is not a client declaration
WRONG child#Child({ msg: string }) READ { msg: string }#msg
MISSING child#Child({ msg: string }) READ child#msg
8 changes: 8 additions & 0 deletions graph/test/typescript/expected/80-vue-sfc.oracle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
oracle=0 engine=7 agree=0 missing=0 (known 0, NEW 0) extra=7
extra Comp.vue#<module-init>() -> Comp.vue#onSave(number)
extra Comp.vue#<module-init>() -> Comp.vue#sfcHelper()
extra Comp.vue#<module-init>() -> child#Child({ msg: string })
extra Comp.vue#<module-init>() -> child#format(number)
extra TemplateFirst.vue#<module-init>() -> TemplateFirst.vue#onInput()
extra TemplateFirst.vue#<module-init>() -> TemplateFirst.vue#shown()
extra TemplateFirst.vue#<module-init>() -> child#MyBadge()
Empty file.
7 changes: 7 additions & 0 deletions graph/test/typescript/expected/80-vue-sfc.types-oracle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
80-vue-sfc [types]
precision 0.0000 (0 correct, 0 wrong)
recall 0.0000 (0 of 0 the compiler resolved)
sites 0 resolved 0
tiers
contexts
not scored: 0 rows whose target is not a client declaration
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ export interface JsFileExtractionOptions {
readonly projectModuleHashes: ReadonlyMap<string, string>;
/** Absolute path -> project-relative path, extension stripped. */
readonly toProjectRelative: (absolutePath: string) => string;
/**
* How `sourceText` parses, when the file's extension cannot say: a `.vue`
* component's virtual script is JS or JSX by its `lang`, not by its name.
*/
readonly scriptKind?: ts.ScriptKind;
}

export interface JsFileFacts {
Expand Down Expand Up @@ -160,7 +165,7 @@ export function extractJavaScriptFile(options: JsFileExtractionOptions): JsFileF
options.sourceText,
ts.ScriptTarget.Latest,
true,
scriptKindFor(options.absoluteFilePath)
options.scriptKind ?? scriptKindFor(options.absoluteFilePath)
);

const moduleResult = extractModule({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
isRequireCall,
pointOf,
} from '@/utils/javascript';
import { resolveVueSpecifier } from '@/utils/vue-sfc';

/**
* `js_import` and `js_export`, minted in a **second pass from expression rows**.
Expand Down Expand Up @@ -1199,7 +1200,10 @@ class JsModuleEdgeExtractor {
undefined,
m
).resolvedModule?.resolvedFileName;
const resolved = resolveIn(mode) ?? (mode === ts.ModuleKind.ESNext ? resolveIn(ts.ModuleKind.CommonJS) : undefined);
// tsc resolves no `.vue` import itself (its extensions are fixed), so a
// component in this program is looked up by path.
const resolved = resolveIn(mode) ?? (mode === ts.ModuleKind.ESNext ? resolveIn(ts.ModuleKind.CommonJS) : undefined)
?? resolveVueSpecifier(specifier, this.options.absoluteFilePath);
if (resolved === undefined) {
return { filePath: '', outcome: JsImportResolutionOutcome.UNRESOLVED_MISSING };
}
Expand Down
11 changes: 9 additions & 2 deletions parser/src/parsers/typescript/extractors/ts-fact-extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
ResolutionStats,
TsLocalResolver,
} from '@/parsers/typescript/extractors/ts-resolution-linker';
import { resolveVueSpecifier } from '@/utils/vue-sfc';

/**
* Extracts the whole fact spine for ONE TypeScript file.
Expand Down Expand Up @@ -91,6 +92,11 @@ export interface TsFileExtractionOptions {
readonly projectModuleHashes: ReadonlyMap<string, string>;
/** Absolute path -> project-relative path, extension stripped. */
readonly toProjectRelative: (absolutePath: string) => string;
/**
* How `sourceText` parses, when the file's extension cannot say: a `.vue`
* component's virtual script is TS or TSX by its `lang`, not by its name.
*/
readonly scriptKind?: ts.ScriptKind;
}

export interface TsFileFacts {
Expand Down Expand Up @@ -142,7 +148,7 @@ export function extractTypeScriptFile(options: TsFileExtractionOptions): TsFileF
options.sourceText,
ts.ScriptTarget.Latest,
true,
scriptKindFor(options.absoluteFilePath)
options.scriptKind ?? scriptKindFor(options.absoluteFilePath)
);

const modules = extractModules({
Expand Down Expand Up @@ -173,7 +179,8 @@ export function extractTypeScriptFile(options: TsFileExtractionOptions): TsFileF
options.compilerOptions,
ts.sys
);
const fileName = resolved.resolvedModule?.resolvedFileName;
const fileName = resolved.resolvedModule?.resolvedFileName
?? resolveVueSpecifier(specifier, options.absoluteFilePath);
if (!fileName) {
return undefined;
}
Expand Down
13 changes: 13 additions & 0 deletions parser/src/parsers/typescript/extractors/ts-import-extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ts from 'typescript';
import { TsImportRegistry } from '@/analysis-types/typescript/TsImportRegistry';
import { TsImportKind, TsImportResolutionKind } from '@/enums/typescript/imports';
import { nodeId } from '@/parsers/typescript/extractors/ts-binder';
import { resolveVueSpecifier, VUE_EXTENSION } from '@/utils/vue-sfc';

/**
* Emits `ts_import` rows — schema §4.12.
Expand Down Expand Up @@ -442,6 +443,18 @@ export class TsImportExtractor {
ts.sys
);
const module = resolved.resolvedModule;
const vueFile = module ? undefined : resolveVueSpecifier(specifier, this.options.absoluteFilePath);
if (vueFile !== undefined && this.options.projectModuleHashes.has(vueFile)) {
// A component in this program; tsc resolves no `.vue` import itself.
return {
absolutePath: vueFile,
relativePath: this.options.toProjectRelative(vueFile),
moduleHash: this.options.projectModuleHashes.get(vueFile)!,
kind: TsImportResolutionKind.RELATIVE_FILE,
extension: VUE_EXTENSION,
packageName: '',
};
}
if (!module) {
// `undefined` is an honest answer, not a failure to try. It is also the
// right answer for a wildcard ambient specifier like `"*.svg"`, which
Expand Down
6 changes: 5 additions & 1 deletion parser/src/parsers/typescript/tsconfig-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ export class TsConfigResolver {
ts.sys,
path.dirname(configPath),
undefined,
configPath
configPath,
undefined,
// `.vue` components are the program's files too, as vue-tsc reads it:
// without this `include: ["src/**/*.vue"]` claims nothing.
[{ extension: '.vue', isMixedContent: true, scriptKind: ts.ScriptKind.Deferred }]
);
result = {
configPath,
Expand Down
Loading
Loading