177 lines
6.5 KiB
JavaScript
177 lines
6.5 KiB
JavaScript
|
|
import { defaultMapperFactory, resolveVueCompilerOptions, setupGlobalTypes, createVueLanguagePlugin, createLanguage, FileMap } from '@vue/language-core';
|
||
|
|
import { createTwoslasher as createTwoslasher$1, defaultCompilerOptions, defaultHandbookOptions, findQueryMarkers, findFlagNotations, getObjectHash } from 'twoslash';
|
||
|
|
import { createPositionConverter, removeCodeRanges, resolveNodePositions } from 'twoslash-protocol';
|
||
|
|
import ts from 'typescript';
|
||
|
|
|
||
|
|
function createTwoslasher(createOptions = {}) {
|
||
|
|
const twoslasherBase = createTwoslasher$1(createOptions);
|
||
|
|
const cache = twoslasherBase.getCacheMap();
|
||
|
|
const tsOptionDeclarations = ts.optionDeclarations;
|
||
|
|
function getVueLanguage(compilerOptions, vueCompilerOptions) {
|
||
|
|
if (!cache)
|
||
|
|
return getLanguage();
|
||
|
|
const key = `vue:${getObjectHash([compilerOptions, vueCompilerOptions])}`;
|
||
|
|
if (!cache.has(key)) {
|
||
|
|
const env = getLanguage();
|
||
|
|
cache.set(key, env);
|
||
|
|
return env;
|
||
|
|
}
|
||
|
|
return cache.get(key);
|
||
|
|
function getLanguage() {
|
||
|
|
const resolvedVueOptions = resolveVueCompilerOptions(vueCompilerOptions);
|
||
|
|
resolvedVueOptions.__setupedGlobalTypes = setupGlobalTypes(ts.sys.getCurrentDirectory(), resolvedVueOptions, ts.sys);
|
||
|
|
const vueLanguagePlugin = createVueLanguagePlugin(ts, defaultCompilerOptions, resolvedVueOptions, (id) => id);
|
||
|
|
return createLanguage(
|
||
|
|
[vueLanguagePlugin],
|
||
|
|
new FileMap(ts.sys.useCaseSensitiveFileNames),
|
||
|
|
() => {
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function twoslasher(code, extension, options = {}) {
|
||
|
|
if (extension !== "vue")
|
||
|
|
return twoslasherBase(code, extension, options);
|
||
|
|
const vueCompilerOptions = {
|
||
|
|
...createOptions.vueCompilerOptions,
|
||
|
|
...options.vueCompilerOptions
|
||
|
|
};
|
||
|
|
const compilerOptions = {
|
||
|
|
...defaultCompilerOptions,
|
||
|
|
...options.compilerOptions
|
||
|
|
};
|
||
|
|
const handbookOptions = {
|
||
|
|
...defaultHandbookOptions,
|
||
|
|
noErrorsCutted: true,
|
||
|
|
...options.handbookOptions
|
||
|
|
};
|
||
|
|
const sourceMeta = {
|
||
|
|
removals: [],
|
||
|
|
positionCompletions: [],
|
||
|
|
positionQueries: [],
|
||
|
|
positionHighlights: [],
|
||
|
|
flagNotations: []
|
||
|
|
};
|
||
|
|
const {
|
||
|
|
customTags = createOptions.customTags || []
|
||
|
|
} = options;
|
||
|
|
const pc = createPositionConverter(code);
|
||
|
|
findQueryMarkers(code, sourceMeta, pc);
|
||
|
|
const flagNotations = findFlagNotations(code, customTags, tsOptionDeclarations);
|
||
|
|
for (const flag of flagNotations) {
|
||
|
|
switch (flag.type) {
|
||
|
|
case "unknown":
|
||
|
|
continue;
|
||
|
|
case "compilerOptions":
|
||
|
|
compilerOptions[flag.name] = flag.value;
|
||
|
|
break;
|
||
|
|
case "handbookOptions":
|
||
|
|
handbookOptions[flag.name] = flag.value;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
sourceMeta.removals.push([flag.start, flag.end]);
|
||
|
|
}
|
||
|
|
let strippedCode = code;
|
||
|
|
for (const [start, end] of sourceMeta.removals) {
|
||
|
|
strippedCode = strippedCode.slice(0, start) + strippedCode.slice(start, end).replace(/\S/g, " ") + strippedCode.slice(end);
|
||
|
|
}
|
||
|
|
const lang = getVueLanguage(compilerOptions, vueCompilerOptions);
|
||
|
|
const sourceScript = lang.scripts.set("index.vue", ts.ScriptSnapshot.fromString(strippedCode));
|
||
|
|
const fileCompiled = get(sourceScript.generated.embeddedCodes.values(), 2);
|
||
|
|
const compiled = fileCompiled.snapshot.getText(0, fileCompiled.snapshot.getLength());
|
||
|
|
const map = defaultMapperFactory(fileCompiled.mappings);
|
||
|
|
function getLastGeneratedOffset(pos) {
|
||
|
|
const offsets = [...map.toGeneratedLocation(pos)];
|
||
|
|
if (!offsets.length)
|
||
|
|
return void 0;
|
||
|
|
return offsets[offsets.length - 1]?.[0];
|
||
|
|
}
|
||
|
|
const result = twoslasherBase(compiled, "tsx", {
|
||
|
|
...options,
|
||
|
|
compilerOptions: {
|
||
|
|
jsx: 1,
|
||
|
|
jsxImportSource: "vue",
|
||
|
|
noImplicitAny: false,
|
||
|
|
...compilerOptions
|
||
|
|
},
|
||
|
|
handbookOptions: {
|
||
|
|
...handbookOptions,
|
||
|
|
keepNotations: true
|
||
|
|
},
|
||
|
|
shouldGetHoverInfo(id) {
|
||
|
|
return !id.startsWith("__VLS");
|
||
|
|
},
|
||
|
|
positionCompletions: sourceMeta.positionCompletions.map((p) => getLastGeneratedOffset(p)),
|
||
|
|
positionQueries: sourceMeta.positionQueries.map((p) => get(map.toGeneratedLocation(p), 0)?.[0]).filter(isNotNull),
|
||
|
|
positionHighlights: sourceMeta.positionHighlights.map(([start, end]) => [
|
||
|
|
get(map.toGeneratedLocation(start), 0)?.[0],
|
||
|
|
get(map.toGeneratedLocation(end), 0)?.[0]
|
||
|
|
]).filter((x) => x[0] != null && x[1] != null)
|
||
|
|
});
|
||
|
|
if (createOptions.debugShowGeneratedCode)
|
||
|
|
return result;
|
||
|
|
const mappedNodes = result.nodes.map((q) => {
|
||
|
|
if ("text" in q && q.text === "any")
|
||
|
|
return void 0;
|
||
|
|
const startMap = get(map.toSourceLocation(q.start), 0);
|
||
|
|
if (!startMap)
|
||
|
|
return void 0;
|
||
|
|
const start = startMap[0];
|
||
|
|
let end = get(map.toSourceLocation(q.start + q.length), 0)?.[0];
|
||
|
|
if (end == null && startMap[1].sourceOffsets[0] === startMap[0])
|
||
|
|
end = startMap[1].sourceOffsets[1];
|
||
|
|
if (end == null || start < 0 || end < 0 || start > end)
|
||
|
|
return void 0;
|
||
|
|
return Object.assign(q, {
|
||
|
|
...q,
|
||
|
|
target: code.slice(start, end),
|
||
|
|
start: startMap[0],
|
||
|
|
length: end - start
|
||
|
|
});
|
||
|
|
}).filter(isNotNull);
|
||
|
|
const mappedRemovals = [
|
||
|
|
...sourceMeta.removals,
|
||
|
|
...result.meta.removals.map((r) => {
|
||
|
|
const start = get(map.toSourceLocation(r[0]), 0)?.[0] ?? code.match(/(?<=<script[\s\S]*>\s)/)?.index;
|
||
|
|
const end = get(map.toSourceLocation(r[1]), 0)?.[0];
|
||
|
|
if (start == null || end == null || start < 0 || end < 0 || start >= end)
|
||
|
|
return void 0;
|
||
|
|
return [start, end];
|
||
|
|
}).filter(isNotNull)
|
||
|
|
];
|
||
|
|
if (!options.handbookOptions?.keepNotations) {
|
||
|
|
const removed = removeCodeRanges(code, mappedRemovals, mappedNodes);
|
||
|
|
result.code = removed.code;
|
||
|
|
result.meta.removals = removed.removals;
|
||
|
|
result.nodes = resolveNodePositions(removed.nodes, result.code);
|
||
|
|
} else {
|
||
|
|
result.meta.removals = mappedRemovals;
|
||
|
|
}
|
||
|
|
result.nodes = result.nodes.filter((n, idx) => {
|
||
|
|
const next = result.nodes[idx + 1];
|
||
|
|
if (!next)
|
||
|
|
return true;
|
||
|
|
if (next.type === n.type && next.start === n.start)
|
||
|
|
return false;
|
||
|
|
return true;
|
||
|
|
});
|
||
|
|
result.meta.extension = "vue";
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
twoslasher.getCacheMap = twoslasherBase.getCacheMap;
|
||
|
|
return twoslasher;
|
||
|
|
}
|
||
|
|
const createTwoslasherVue = createTwoslasher;
|
||
|
|
function isNotNull(x) {
|
||
|
|
return x != null;
|
||
|
|
}
|
||
|
|
function get(iterator, index) {
|
||
|
|
for (const item of iterator) {
|
||
|
|
if (index-- === 0)
|
||
|
|
return item;
|
||
|
|
}
|
||
|
|
return void 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
export { createTwoslasher, createTwoslasherVue };
|