@@ -23,6 +23,32 @@ import {
23
23
24
24
export type ConfigType = 'js' | 'ts' ;
25
25
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
+
26
52
/**
27
53
* ProjectManager translates VFS files to one or many projects denoted by [tj]config.json.
28
54
* It uses either local or remote file system to fetch directory tree and files from and then
@@ -852,9 +878,124 @@ export class ProjectConfiguration {
852
878
this . logger
853
879
) ;
854
880
this . service = ts . createLanguageService ( this . host , this . documentRegistry ) ;
881
+ this . enablePlugins ( options ) ;
855
882
this . initialized = true ;
856
883
}
857
884
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
+
858
999
/**
859
1000
* Ensures we are ready to process files from a given sub-project
860
1001
*/
0 commit comments