Skip to content
This repository was archived by the owner on Oct 16, 2020. It is now read-only.

Commit 1c2e75e

Browse files
committed
feat: hackish implementation of plugin loading for tslint
1 parent 8591294 commit 1c2e75e

File tree

4 files changed

+148
-10
lines changed

4 files changed

+148
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"source-map-support": "^0.4.11",
7979
"temp": "^0.8.3",
8080
"tslint": "^5.0.0",
81+
"tslint-language-service": "^0.9.6",
8182
"validate-commit-msg": "^2.12.2"
8283
},
8384
"bin": {

src/project-manager.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,32 @@ import {
2323

2424
export type ConfigType = 'js' | 'ts';
2525

26+
interface Project {
27+
projectService: {
28+
logger: Logger;
29+
};
30+
}
31+
32+
type ServerHost = any;
33+
34+
type RequireResult = { module: {}, error: undefined } | { module: undefined, error: {} };
35+
36+
// copied from TypeScript server/project.ts
37+
interface PluginCreateInfo {
38+
project: Project;
39+
languageService: ts.LanguageService;
40+
languageServiceHost: ts.LanguageServiceHost;
41+
serverHost: ServerHost;
42+
config: any;
43+
}
44+
45+
interface PluginModule {
46+
create(createInfo: PluginCreateInfo): ts.LanguageService;
47+
getExternalFiles?(proj: Project): string[];
48+
}
49+
50+
type PluginModuleFactory = (mod: { typescript: typeof ts }) => PluginModule;
51+
2652
/**
2753
* ProjectManager translates VFS files to one or many projects denoted by [tj]config.json.
2854
* It uses either local or remote file system to fetch directory tree and files from and then
@@ -852,9 +878,124 @@ export class ProjectConfiguration {
852878
this.logger
853879
);
854880
this.service = ts.createLanguageService(this.host, this.documentRegistry);
881+
this.enablePlugins(options);
855882
this.initialized = true;
856883
}
857884

885+
private serverHostRequire(initialDir: string, moduleName: string): RequireResult {
886+
// const modulePath = ts.resolveJavaScriptModule(moduleName, initialDir, this.fs));
887+
const modulePath = initialDir + '/' + moduleName;
888+
try {
889+
return { module: require(modulePath), error: undefined };
890+
} catch (error) {
891+
return { module: undefined, error };
892+
}
893+
}
894+
895+
// // marked @internal in ts :(
896+
// private resolveJavaScriptModule(moduleName: string, initialDir: string, host: ts.ModuleResolutionHost): string {
897+
// const { resolvedModule, failedLookupLocations } =
898+
// ts.nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*jsOnly*/ true);
899+
// if (!resolvedModule) {
900+
// throw new Error(`Could not resolve JS module ${moduleName} starting at ${initialDir}. Looked in: ${failedLookupLocations.join(', ')}`);
901+
// }
902+
// return resolvedModule.resolvedFileName;
903+
// }
904+
905+
enablePlugins(options: ts.CompilerOptions) {
906+
// const host = this.getHost();
907+
// const options = this.getCompilerOptions();
908+
909+
// if (!host.require) {
910+
// this.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
911+
// return;
912+
// }
913+
914+
// Search our peer node_modules, then any globally-specified probe paths
915+
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
916+
// const searchPaths = [combinePaths(host.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations];
917+
const searchPaths = [];
918+
// if (this.projectService.allowLocalPluginLoads) {
919+
// const local = ts.getDirectoryPath(this.configFilePath);
920+
const local = this.rootFilePath;
921+
this.logger.info(`enablePlugins for ${this.configFilePath}`);
922+
this.logger.info(`Local plugin loading enabled; adding ${local} to search paths`);
923+
searchPaths.unshift(local);
924+
// }
925+
926+
// Enable tsconfig-specified plugins
927+
if (options.plugins) {
928+
for (const pluginConfigEntry of options.plugins as ts.PluginImport[]) {
929+
this.enablePlugin(pluginConfigEntry, searchPaths);
930+
}
931+
}
932+
const file = '';
933+
file.toLowerCase();
934+
935+
// if (this.projectService.globalPlugins) {
936+
// // Enable global plugins with synthetic configuration entries
937+
// for (const globalPluginName of this.projectService.globalPlugins) {
938+
// // Skip already-locally-loaded plugins
939+
// if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue;
940+
941+
// // Provide global: true so plugins can detect why they can't find their config
942+
// this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
943+
// }
944+
// }
945+
}
946+
947+
private resolveModule(moduleName: string, initialDir: string): {} | undefined {
948+
// const resolvedPath = ts.normalizeSlashes(host.resolvePath(ts.combinePaths(initialDir, 'node_modules')));
949+
const pathResolver = initialDir.includes('\\') ? path.win32 : path.posix;
950+
951+
const resolvedPath = pathResolver.resolve(initialDir, 'node_modules');
952+
this.logger.info(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
953+
const result = this.serverHostRequire(resolvedPath, moduleName);
954+
if (result.error) {
955+
this.logger.info(`Failed to load module: ${JSON.stringify(result.error)}`);
956+
return undefined;
957+
}
958+
return result.module;
959+
}
960+
961+
private enablePlugin(pluginConfigEntry: ts.PluginImport, searchPaths: string[]) {
962+
// const log = (message: string) => {
963+
// this.logger.info(message);
964+
// };
965+
966+
for (const searchPath of searchPaths) {
967+
const resolvedModule = this.resolveModule(pluginConfigEntry.name, searchPath) as PluginModuleFactory;
968+
if (resolvedModule) {
969+
this.enableProxy(resolvedModule, pluginConfigEntry);
970+
return;
971+
}
972+
}
973+
this.logger.info(`Couldn't find ${pluginConfigEntry.name} anywhere in paths: ${searchPaths.join(',')}`);
974+
}
975+
976+
private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: ts.PluginImport) {
977+
try {
978+
if (typeof pluginModuleFactory !== 'function') {
979+
this.logger.info(`Skipped loading plugin ${configEntry.name} because it did expose a proper factory function`);
980+
return;
981+
}
982+
983+
const info: PluginCreateInfo = {
984+
config: configEntry,
985+
project: { projectService: { logger: this.logger }}, // TODO: this will never work.
986+
languageService: this.getService(),
987+
languageServiceHost: this.getHost(),
988+
serverHost: undefined // TODO: may need to be faked.
989+
};
990+
991+
const pluginModule = pluginModuleFactory({ typescript: ts });
992+
this.service = pluginModule.create(info);
993+
// this.plugins.push(pluginModule); TODO: is this needed?
994+
} catch (e) {
995+
this.logger.info(`Plugin activation failed: ${e}`);
996+
}
997+
}
998+
858999
/**
8591000
* Ensures we are ready to process files from a given sub-project
8601001
*/

src/typescript-service.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,15 +1301,8 @@ export class TypeScriptService {
13011301
if (!config) {
13021302
return;
13031303
}
1304-
const program = config.getProgram(span);
1305-
if (!program) {
1306-
return;
1307-
}
1308-
const sourceFile = program.getSourceFile(uri2path(uri));
1309-
if (!sourceFile) {
1310-
return;
1311-
}
1312-
const tsDiagnostics = ts.getPreEmitDiagnostics(program, sourceFile);
1304+
const fileName = uri2path(uri);
1305+
const tsDiagnostics = config.getService().getSyntacticDiagnostics(fileName).concat(config.getService().getSemanticDiagnostics(fileName));
13131306
const diagnostics = iterate(tsDiagnostics)
13141307
// TS can report diagnostics without a file and range in some cases
13151308
// These cannot be represented as LSP Diagnostics since the range and URI is required

tsconfig.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
"noImplicitThis": true,
1717
"noUnusedLocals": true,
1818
"noImplicitAny": true,
19-
"strictNullChecks": true
19+
"strictNullChecks": true,
20+
"plugins": [
21+
{ "name": "tslint-language-service"}
22+
]
2023
},
2124
"include": [
2225
"src/**/*.ts"

0 commit comments

Comments
 (0)