Skip to content

Expose compiler API needed for vue #14888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
69 changes: 69 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,75 @@ namespace Harness.LanguageService {
}),
error: undefined
};
case "mock-vue":
return {
module: () => ({
create(info: ts.server.PluginCreateInfo) {
const compilerOptions: ts.CompilerOptions = {
allowNonTsExtensions: true,
allowJs: true,
lib: ["lib.dom.d.ts", "lib.es2017.d.ts"],
target: ts.ScriptTarget.Latest,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
allowSyntheticDefaultImports: true
};
info.languageServiceHost.resolveModuleNames = (moduleNames, containingFile) =>
bifilterMap(moduleNames, importInterested,
name => ({
resolvedFileName: containingFile.slice(0, containingFile.lastIndexOf("/")) + "/" + name,
extension: ts.Extension.Ts
}),
name => ts.resolveModuleName(name, containingFile, compilerOptions, ts.sys).resolvedModule);
const clssf = ts.createLanguageServiceSourceFile;
const ulssf = ts.updateLanguageServiceSourceFile;
ts.overrideCreateupdateLanguageServiceSourceFile(
(fileName, scriptSnapshot, scriptTarget, version, setNodeParents, scriptKind?) => {
if (interested(fileName)) {
const wrapped = scriptSnapshot;
scriptSnapshot = {
getChangeRange: old => wrapped.getChangeRange(old),
getLength: () => wrapped.getLength(),
getText: (start, end) => parse(wrapped.getText(0, wrapped.getLength())).slice(start, end),
};
}
return clssf(fileName, scriptSnapshot, scriptTarget, version, setNodeParents, scriptKind);
},
(sourceFile, scriptSnapshot, version, textChangeRange, aggressiveChecks?) => {
if (interested(sourceFile.fileName)) {
const wrapped = scriptSnapshot;
scriptSnapshot = {
getChangeRange: old => wrapped.getChangeRange(old),
getLength: () => wrapped.getLength(),
getText: (start, end) => parse(wrapped.getText(0, wrapped.getLength())).slice(start, end),
};
}
return ulssf(sourceFile, scriptSnapshot, version, textChangeRange, aggressiveChecks);
});
return makeDefaultProxy(info);

function bifilterMap<T, U>(l: T[], predicate: (t: T) => boolean, yes: (t: T) => U, no: (t: T) => U): U[] {
const result = [];
for (const x of l) {
result.push(predicate(x) ? yes(x) : no(x));
}
return result;
}
function interested(fileName: string) {
return fileName.slice(-4) === ".vue";
}
function parse(text: string) {
const start = text.indexOf("<script>") + "<script>".length;
const end = text.indexOf("</script>");
return text.slice(0, start).replace(/./g, " ") + text.slice(start, end) + text.slice(end).replace(/./g, " ");
}
function importInterested(fileName: string) {
return fileName.charAt(0) === "." && fileName.slice(-4) === ".vue";
}
}
}),
error: undefined
};

default:
return {
Expand Down
2 changes: 1 addition & 1 deletion src/server/lsHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,4 @@ namespace ts.server {
this.compilationSettings = opt;
}
}
}
}
2 changes: 1 addition & 1 deletion src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1083,4 +1083,4 @@ namespace ts.server {
this.typeAcquisition = newTypeAcquisition;
}
}
}
}
7 changes: 7 additions & 0 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,13 @@ namespace ts {
sourceFile.scriptSnapshot = scriptSnapshot;
}

export function overrideCreateupdateLanguageServiceSourceFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a LanguageServiceHost optional functions. we do not want to override this for all instances of the language service.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also recommend splitting this into createSoruceFile and upadateSourceFile.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are global functions, so I'm not sure how to avoid overriding them for all instances of the language service. (That was probably the cause of the CI failures; bad overrides are also global.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have two options:

  1. in LanguageServiceHost:
export interface LanguageServiceHost {
    ...
    createSourceFile?(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
    updateSourceFile?(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile;
    ....
}

Then in the language service, in places where we call ts.createLanguageServiceSourceFile or ts.updateLanguageServiceSourceFile, we can just check the host if it supports these APIs.

  1. users can wrap the DocumentRegistry for the language service, and do the needed work in acquireDocument and updateDocument.

create: (fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind, cheat?: string) => SourceFile,
update: (sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean, cheat?: string) => SourceFile) {
ts.createLanguageServiceSourceFile = create;
ts.updateLanguageServiceSourceFile = update;
}

export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile {
const text = scriptSnapshot.getText(0, scriptSnapshot.getLength());
const sourceFile = createSourceFile(fileName, text, scriptTarget, setNodeParents, scriptKind);
Expand Down
44 changes: 44 additions & 0 deletions tests/cases/fourslash/server/vueProxy1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// <reference path="../fourslash.ts"/>

// @Filename: tsconfig.json
//// {
//// "compilerOptions": {
//// "allowNonTsExtensions": true,
//// "plugins": [
//// { "name": "mock-vue" }
//// ]
//// },
//// "files": ["a.vue"]
//// }

// Note: This test does *not* implement the correct vue transformation.
// So it's other.data.property, not other.property or other.$data.property
// @Filename: other.vue
////<template>
////</template>
////<script>
////export default {
//// data: { property: "Example" }
////}
////</script>
////<style>
////</style>


// @Filename: a.vue
////<template>
////</template>
////<script>
////import other from './other.vue'
//// other.data.property/**/
////export default {
//// data: { greeting: "Hello" }
////}
////</script>
////<style>
////</style>

// LS shouldn't crash/fail if a plugin fails to init correctly
goTo.marker();
verify.quickInfoIs('(property) property: string');
verify.numberOfErrorsInCurrentFile(0);