@@ -6,13 +6,22 @@ import {
6
6
getTypeChecker ,
7
7
isPositionInsideNode ,
8
8
} from '../utils'
9
-
10
9
import type tsModule from 'typescript/lib/tsserverlibrary'
11
10
12
11
const TYPE_ANNOTATION = ': Metadata | null'
13
12
const TYPE_ANNOTATION_ASYNC = ': Promise<Metadata | null>'
14
13
const TYPE_IMPORT = `\n\nimport type { Metadata } from 'next'`
15
14
15
+ const updatedFilePositionsCache = new Map < string , number [ ] > ( )
16
+
17
+ function cacheKey (
18
+ fileName : string ,
19
+ isFunction : boolean ,
20
+ isGenerateMetadata ?: boolean
21
+ ) {
22
+ return `${ fileName } :${ isFunction ? 'function' : 'variable' } :${ isGenerateMetadata ? 'generateMetadata' : 'metadata' } `
23
+ }
24
+
16
25
// Find the `export const metadata = ...` node.
17
26
function getMetadataExport ( fileName : string , position : number ) {
18
27
const source = getSource ( fileName )
@@ -49,139 +58,67 @@ function getMetadataExport(fileName: string, position: number) {
49
58
return metadataExport
50
59
}
51
60
52
- let cachedProxiedLanguageService : tsModule . LanguageService | undefined
53
- let cachedProxiedLanguageServiceHost : tsModule . LanguageServiceHost | undefined
54
- function getProxiedLanguageService ( ) {
55
- if ( cachedProxiedLanguageService )
56
- return {
57
- languageService : cachedProxiedLanguageService as tsModule . LanguageService ,
58
- languageServiceHost :
59
- cachedProxiedLanguageServiceHost as tsModule . LanguageServiceHost & {
60
- addFile : ( fileName : string , body : string ) => void
61
- } ,
62
- }
61
+ function updateVirtualFileWithType (
62
+ fileName : string ,
63
+ node : tsModule . VariableDeclaration | tsModule . FunctionDeclaration ,
64
+ isGenerateMetadata ?: boolean
65
+ ) {
66
+ const ts = getTs ( )
67
+ const isFunction = ts . isFunctionDeclaration ( node )
68
+ const key = cacheKey ( fileName , isFunction , isGenerateMetadata )
63
69
64
- const languageServiceHost = getInfo ( ) . languageServiceHost
70
+ if ( updatedFilePositionsCache . has ( key ) ) {
71
+ return updatedFilePositionsCache . get ( key )
72
+ }
65
73
66
- const ts = getTs ( )
67
- class ProxiedLanguageServiceHost implements tsModule . LanguageServiceHost {
68
- files : {
69
- [ fileName : string ] : { file : tsModule . IScriptSnapshot ; ver : number }
70
- } = { }
71
-
72
- log = ( ) => { }
73
- trace = ( ) => { }
74
- error = ( ) => { }
75
- getCompilationSettings = ( ) => languageServiceHost . getCompilationSettings ( )
76
- getScriptIsOpen = ( ) => true
77
- getCurrentDirectory = ( ) => languageServiceHost . getCurrentDirectory ( )
78
- getDefaultLibFileName = ( o : any ) =>
79
- languageServiceHost . getDefaultLibFileName ( o )
80
-
81
- getScriptVersion = ( fileName : string ) => {
82
- const file = this . files [ fileName ]
83
- if ( ! file ) return languageServiceHost . getScriptVersion ( fileName )
84
- return file . ver . toString ( )
85
- }
74
+ let nodeEnd : number
86
75
87
- getScriptSnapshot = ( fileName : string ) => {
88
- const file = this . files [ fileName ]
89
- if ( ! file ) return languageServiceHost . getScriptSnapshot ( fileName )
90
- return file . file
91
- }
76
+ if ( isFunction ) {
77
+ nodeEnd = node . body ! . getFullStart ( )
78
+ } else {
79
+ nodeEnd = node . name . getFullStart ( ) + node . name . getFullWidth ( )
80
+ }
92
81
93
- getScriptFileNames ( ) : string [ ] {
94
- const names : Set < string > = new Set ( )
95
- for ( var name in this . files ) {
96
- if ( this . files . hasOwnProperty ( name ) ) {
97
- names . add ( name )
98
- }
99
- }
100
- const files = languageServiceHost . getScriptFileNames ( )
101
- for ( const file of files ) {
102
- names . add ( file )
103
- }
104
- return [ ...names ]
105
- }
82
+ // If the node is already typed, we don't need to do anything
83
+ if ( ! isTyped ( node ) ) {
84
+ const source = getSource ( fileName )
85
+ if ( ! source ) return
106
86
107
- addFile ( fileName : string , body : string ) {
108
- const snap = ts . ScriptSnapshot . fromString ( body )
109
- snap . getChangeRange = ( _ ) => undefined
110
- const existing = this . files [ fileName ]
111
- if ( existing ) {
112
- this . files [ fileName ] . ver ++
113
- this . files [ fileName ] . file = snap
87
+ // We annotate with the type in a virtual language service
88
+ const sourceText = source . getFullText ( )
89
+ let annotation : string
90
+
91
+ if ( isFunction ) {
92
+ if ( isGenerateMetadata ) {
93
+ const isAsync = node . modifiers ?. some (
94
+ ( m ) => m . kind === ts . SyntaxKind . AsyncKeyword
95
+ )
96
+ annotation = isAsync ? TYPE_ANNOTATION_ASYNC : TYPE_ANNOTATION
114
97
} else {
115
- this . files [ fileName ] = { ver : 1 , file : snap }
98
+ return
116
99
}
100
+ } else {
101
+ annotation = TYPE_ANNOTATION
117
102
}
118
103
119
- readFile ( fileName : string ) {
120
- const file = this . files [ fileName ]
121
- return file
122
- ? file . file . getText ( 0 , file . file . getLength ( ) )
123
- : languageServiceHost . readFile ( fileName )
124
- }
125
- fileExists ( fileName : string ) {
126
- return (
127
- this . files [ fileName ] !== undefined ||
128
- languageServiceHost . fileExists ( fileName )
129
- )
130
- }
131
- }
104
+ const newSource =
105
+ sourceText . slice ( 0 , nodeEnd ) +
106
+ annotation +
107
+ sourceText . slice ( nodeEnd ) +
108
+ TYPE_IMPORT
132
109
133
- cachedProxiedLanguageServiceHost = new ProxiedLanguageServiceHost ( )
134
- cachedProxiedLanguageService = ts . createLanguageService (
135
- cachedProxiedLanguageServiceHost ,
136
- ts . createDocumentRegistry ( )
137
- )
138
- return {
139
- languageService : cachedProxiedLanguageService as tsModule . LanguageService ,
140
- languageServiceHost :
141
- cachedProxiedLanguageServiceHost as tsModule . LanguageServiceHost & {
142
- addFile : ( fileName : string , body : string ) => void
143
- } ,
144
- }
145
- }
110
+ const { languageServiceHost } = getInfo ( )
111
+ // Add the file to the virtual language service
112
+ // This will trigger TypeScript to re-analyze the workspace
113
+ languageServiceHost . addFile ( fileName , newSource )
146
114
147
- function updateVirtualFileWithType (
148
- fileName : string ,
149
- node : tsModule . VariableDeclaration | tsModule . FunctionDeclaration ,
150
- isGenerateMetadata ?: boolean
151
- ) {
152
- const source = getSource ( fileName )
153
- if ( ! source ) return
154
-
155
- // We annotate with the type in a virtual language service
156
- const sourceText = source . getFullText ( )
157
- let nodeEnd : number
158
- let annotation : string
115
+ const pos = [ nodeEnd , annotation . length ]
116
+ updatedFilePositionsCache . set ( key , pos )
159
117
160
- const ts = getTs ( )
161
- if ( ts . isFunctionDeclaration ( node ) ) {
162
- if ( isGenerateMetadata ) {
163
- nodeEnd = node . body ! . getFullStart ( )
164
- const isAsync = node . modifiers ?. some (
165
- ( m ) => m . kind === ts . SyntaxKind . AsyncKeyword
166
- )
167
- annotation = isAsync ? TYPE_ANNOTATION_ASYNC : TYPE_ANNOTATION
168
- } else {
169
- return
170
- }
171
- } else {
172
- nodeEnd = node . name . getFullStart ( ) + node . name . getFullWidth ( )
173
- annotation = TYPE_ANNOTATION
118
+ return pos
174
119
}
175
120
176
- const newSource =
177
- sourceText . slice ( 0 , nodeEnd ) +
178
- annotation +
179
- sourceText . slice ( nodeEnd ) +
180
- TYPE_IMPORT
181
- const { languageServiceHost } = getProxiedLanguageService ( )
182
- languageServiceHost . addFile ( fileName , newSource )
183
-
184
- return [ nodeEnd , annotation . length ]
121
+ return [ nodeEnd , 0 ]
185
122
}
186
123
187
124
function isTyped (
@@ -196,7 +133,7 @@ function proxyDiagnostics(
196
133
n : tsModule . VariableDeclaration | tsModule . FunctionDeclaration
197
134
) {
198
135
// Get diagnostics
199
- const { languageService } = getProxiedLanguageService ( )
136
+ const { languageService } = getInfo ( )
200
137
const diagnostics = languageService . getSemanticDiagnostics ( fileName )
201
138
const source = getSource ( fileName )
202
139
@@ -225,26 +162,25 @@ const metadata = {
225
162
filterCompletionsAtPosition (
226
163
fileName : string ,
227
164
position : number ,
228
- _options : any ,
165
+ options : any ,
229
166
prior : tsModule . WithMetadata < tsModule . CompletionInfo >
230
167
) {
231
168
const node = getMetadataExport ( fileName , position )
232
169
if ( ! node ) return prior
233
- if ( isTyped ( node ) ) return prior
234
170
235
- const ts = getTs ( )
171
+ const { languageService } = getInfo ( )
236
172
173
+ const ts = getTs ( )
237
174
// We annotate with the type in a virtual language service
238
175
const pos = updateVirtualFileWithType ( fileName , node )
239
176
if ( pos === undefined ) return prior
240
177
241
178
// Get completions
242
- const { languageService } = getProxiedLanguageService ( )
243
179
const newPos = position <= pos [ 0 ] ? position : position + pos [ 1 ]
244
180
const completions = languageService . getCompletionsAtPosition (
245
181
fileName ,
246
182
newPos ,
247
- undefined
183
+ options
248
184
)
249
185
250
186
if ( completions ) {
@@ -465,7 +401,7 @@ const metadata = {
465
401
const pos = updateVirtualFileWithType ( fileName , node )
466
402
if ( pos === undefined ) return
467
403
468
- const { languageService } = getProxiedLanguageService ( )
404
+ const { languageService } = getInfo ( )
469
405
const newPos = position <= pos [ 0 ] ? position : position + pos [ 1 ]
470
406
471
407
const details = languageService . getCompletionEntryDetails (
@@ -489,7 +425,7 @@ const metadata = {
489
425
const pos = updateVirtualFileWithType ( fileName , node )
490
426
if ( pos === undefined ) return
491
427
492
- const { languageService } = getProxiedLanguageService ( )
428
+ const { languageService } = getInfo ( )
493
429
const newPos = position <= pos [ 0 ] ? position : position + pos [ 1 ]
494
430
const insight = languageService . getQuickInfoAtPosition ( fileName , newPos )
495
431
return insight
@@ -503,7 +439,7 @@ const metadata = {
503
439
// We annotate with the type in a virtual language service
504
440
const pos = updateVirtualFileWithType ( fileName , node )
505
441
if ( pos === undefined ) return
506
- const { languageService } = getProxiedLanguageService ( )
442
+ const { languageService } = getInfo ( )
507
443
const newPos = position <= pos [ 0 ] ? position : position + pos [ 1 ]
508
444
509
445
const definitionInfoAndBoundSpan =
0 commit comments