@@ -7,8 +7,8 @@ import * as ts from 'typescript';
7
7
import { Disposable } from './disposable' ;
8
8
import { FileSystemUpdater } from './fs' ;
9
9
import { Logger , NoopLogger } from './logging' ;
10
- import { combinePaths } from './match-files' ;
11
10
import { InMemoryFileSystem } from './memfs' ;
11
+ import { PluginCreateInfo , PluginLoader , PluginModuleFactory } from './plugins' ;
12
12
import { InitializationOptions } from './request-type' ;
13
13
import { traceObservable , traceSync } from './tracing' ;
14
14
import {
@@ -25,52 +25,6 @@ import {
25
25
26
26
export type ConfigType = 'js' | 'ts' ;
27
27
28
- // definitions from from TypeScript server/project.ts
29
-
30
- /**
31
- * A plugin exports an initialization function, injected with
32
- * the current typescript instance
33
- */
34
- type PluginModuleFactory = ( mod : { typescript : typeof ts } ) => PluginModule ;
35
-
36
- /**
37
- * A plugin presents this API when initialized
38
- */
39
- interface PluginModule {
40
- create ( createInfo : PluginCreateInfo ) : ts . LanguageService ;
41
- getExternalFiles ?( proj : Project ) : string [ ] ;
42
- }
43
-
44
- /**
45
- * All of tsserver's environment exposed to plugins
46
- */
47
- interface PluginCreateInfo {
48
- project : Project ;
49
- languageService : ts . LanguageService ;
50
- languageServiceHost : ts . LanguageServiceHost ;
51
- serverHost : ServerHost ;
52
- config : any ;
53
- }
54
-
55
- /**
56
- * The portion of tsserver's Project API exposed to plugins
57
- */
58
- interface Project {
59
- projectService : {
60
- logger : Logger ;
61
- } ;
62
- }
63
-
64
- /**
65
- * The portion of tsserver's ServerHost API exposed to plugins
66
- */
67
- type ServerHost = object ;
68
-
69
- /**
70
- * The result of a node require: a module or an error.
71
- */
72
- type RequireResult = { module : { } , error : undefined } | { module : undefined , error : { } } ;
73
-
74
28
/**
75
29
* ProjectManager translates VFS files to one or many projects denoted by [tj]config.json.
76
30
* It uses either local or remote file system to fetch directory tree and files from and then
@@ -780,10 +734,6 @@ export class ProjectConfiguration {
780
734
*/
781
735
private traceModuleResolution : boolean ;
782
736
783
- private allowLocalPluginLoads : boolean = false ;
784
- private globalPlugins : string [ ] = [ ] ;
785
- private pluginProbeLocations : string [ ] = [ ] ;
786
-
787
737
/**
788
738
* Root file path, relative to workspace hierarchy root
789
739
*/
@@ -810,18 +760,13 @@ export class ProjectConfiguration {
810
760
configFilePath : string ,
811
761
configContent ?: any ,
812
762
traceModuleResolution ?: boolean ,
813
- initializationOptions ?: InitializationOptions ,
763
+ private initializationOptions ?: InitializationOptions ,
814
764
private logger : Logger = new NoopLogger ( )
815
765
) {
816
766
this . fs = fs ;
817
767
this . configFilePath = configFilePath ;
818
768
this . configContent = configContent ;
819
769
this . versions = versions ;
820
- if ( initializationOptions ) {
821
- this . allowLocalPluginLoads = initializationOptions . allowLocalPluginLoads || false ;
822
- this . globalPlugins = initializationOptions . globalPlugins || [ ] ;
823
- this . pluginProbeLocations = initializationOptions . pluginProbeLocations || [ ] ;
824
- }
825
770
this . traceModuleResolution = traceModuleResolution || false ;
826
771
this . rootFilePath = rootFilePath ;
827
772
}
@@ -919,110 +864,11 @@ export class ProjectConfiguration {
919
864
this . logger
920
865
) ;
921
866
this . service = ts . createLanguageService ( this . host , this . documentRegistry ) ;
922
- this . enablePlugins ( options ) ;
867
+ const pluginLoader = new PluginLoader ( this . rootFilePath , this . fs , this . initializationOptions , this . logger ) ;
868
+ pluginLoader . loadPlugins ( options , this . enableProxy . bind ( this ) ) ;
923
869
this . initialized = true ;
924
870
}
925
871
926
- private enablePlugins ( options : ts . CompilerOptions ) {
927
- // Search our peer node_modules, then any globally-specified probe paths
928
- // ../../.. to walk from X/node_modules/javascript-typescript-langserver/lib/project-manager.js to X/node_modules/
929
- const searchPaths = [ combinePaths ( __filename , '../../..' ) , ...this . pluginProbeLocations ] ;
930
-
931
- // Corresponds to --allowLocalPluginLoads, opt-in to avoid remote code execution.
932
- if ( this . allowLocalPluginLoads ) {
933
- const local = this . rootFilePath ;
934
- this . logger . info ( `Local plugin loading enabled; adding ${ local } to search paths` ) ;
935
- searchPaths . unshift ( local ) ;
936
- }
937
-
938
- let pluginImports : ts . PluginImport [ ] = [ ] ;
939
- if ( options . plugins ) {
940
- pluginImports = options . plugins as ts . PluginImport [ ] ;
941
- }
942
-
943
- // Enable tsconfig-specified plugins
944
- if ( options . plugins ) {
945
- for ( const pluginConfigEntry of pluginImports ) {
946
- this . enablePlugin ( pluginConfigEntry , searchPaths ) ;
947
- }
948
- }
949
-
950
- if ( this . globalPlugins ) {
951
- // Enable global plugins with synthetic configuration entries
952
- for ( const globalPluginName of this . globalPlugins ) {
953
- // Skip already-locally-loaded plugins
954
- if ( ! pluginImports || pluginImports . some ( p => p . name === globalPluginName ) ) {
955
- continue ;
956
- }
957
-
958
- // Provide global: true so plugins can detect why they can't find their config
959
- this . enablePlugin ( { name : globalPluginName , global : true } as ts . PluginImport , searchPaths ) ;
960
- }
961
- }
962
- }
963
-
964
- /**
965
- * Tries to load and enable a single plugin
966
- * @param pluginConfigEntry
967
- * @param searchPaths
968
- */
969
- private enablePlugin ( pluginConfigEntry : ts . PluginImport , searchPaths : string [ ] ) {
970
- for ( const searchPath of searchPaths ) {
971
- const resolvedModule = this . resolveModule ( pluginConfigEntry . name , searchPath ) as PluginModuleFactory ;
972
- if ( resolvedModule ) {
973
- this . enableProxy ( resolvedModule , pluginConfigEntry ) ;
974
- return ;
975
- }
976
- }
977
- this . logger . info ( `Couldn't find ${ pluginConfigEntry . name } anywhere in paths: ${ searchPaths . join ( ',' ) } ` ) ;
978
- }
979
-
980
- /**
981
- * Load a plugin use a node require
982
- * @param moduleName
983
- * @param initialDir
984
- */
985
- private resolveModule ( moduleName : string , initialDir : string ) : { } | undefined {
986
- this . logger . info ( `Loading ${ moduleName } from ${ initialDir } ` ) ;
987
- const result = this . requirePlugin ( initialDir , moduleName ) ;
988
- if ( result . error ) {
989
- this . logger . info ( `Failed to load module: ${ JSON . stringify ( result . error ) } ` ) ;
990
- return undefined ;
991
- }
992
- return result . module ;
993
- }
994
-
995
- /**
996
- * Resolves a loads a plugin function relative to initialDir
997
- * @param initialDir
998
- * @param moduleName
999
- */
1000
- private requirePlugin ( initialDir : string , moduleName : string ) : RequireResult {
1001
- const modulePath = this . resolveJavaScriptModule ( moduleName , initialDir , this . fs ) ;
1002
- try {
1003
- return { module : require ( modulePath ) , error : undefined } ;
1004
- } catch ( error ) {
1005
- return { module : undefined , error } ;
1006
- }
1007
- }
1008
-
1009
- /**
1010
- * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations.
1011
- * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963
1012
- * Throws an error if the module can't be resolved.
1013
- * stolen from moduleNameResolver.ts because marked as internal
1014
- */
1015
- private resolveJavaScriptModule ( moduleName : string , initialDir : string , host : ts . ModuleResolutionHost ) : string {
1016
- // TODO: this should set jsOnly=true to the internal resolver, but this parameter is not exposed on a public api.
1017
- const result =
1018
- ts . nodeModuleNameResolver ( moduleName , /* containingFile */ initialDir . replace ( '\\' , '/' ) + '/package.json' , { moduleResolution : ts . ModuleResolutionKind . NodeJs , allowJs : true } , this . fs , undefined ) ;
1019
- if ( ! result . resolvedModule ) {
1020
- // this.logger.error(result.failedLookupLocations);
1021
- throw new Error ( `Could not resolve JS module ${ moduleName } starting at ${ initialDir } .` ) ;
1022
- }
1023
- return result . resolvedModule . resolvedFileName ;
1024
- }
1025
-
1026
872
/**
1027
873
* Replaces the LanguageService with an instance wrapped by the plugin
1028
874
* @param pluginModuleFactory function to create the module
0 commit comments