1
1
// Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers.
2
2
/* @internal */
3
3
namespace ts . moduleSpecifiers {
4
- const enum RelativePreference { Relative , NonRelative , Auto }
4
+ const enum RelativePreference { Relative , NonRelative , Shortest , ExternalNonRelative }
5
5
// See UserPreferences#importPathEnding
6
6
const enum Ending { Minimal , Index , JsExtension }
7
7
@@ -13,7 +13,11 @@ namespace ts.moduleSpecifiers {
13
13
14
14
function getPreferences ( { importModuleSpecifierPreference, importModuleSpecifierEnding } : UserPreferences , compilerOptions : CompilerOptions , importingSourceFile : SourceFile ) : Preferences {
15
15
return {
16
- relativePreference : importModuleSpecifierPreference === "relative" ? RelativePreference . Relative : importModuleSpecifierPreference === "non-relative" ? RelativePreference . NonRelative : RelativePreference . Auto ,
16
+ relativePreference :
17
+ importModuleSpecifierPreference === "relative" ? RelativePreference . Relative :
18
+ importModuleSpecifierPreference === "non-relative" ? RelativePreference . NonRelative :
19
+ importModuleSpecifierPreference === "project-relative" ? RelativePreference . ExternalNonRelative :
20
+ RelativePreference . Shortest ,
17
21
ending : getEnding ( ) ,
18
22
} ;
19
23
function getEnding ( ) : Ending {
@@ -147,17 +151,19 @@ namespace ts.moduleSpecifiers {
147
151
148
152
interface Info {
149
153
readonly getCanonicalFileName : GetCanonicalFileName ;
154
+ readonly importingSourceFileName : Path
150
155
readonly sourceDirectory : Path ;
151
156
}
152
157
// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path
153
158
function getInfo ( importingSourceFileName : Path , host : ModuleSpecifierResolutionHost ) : Info {
154
159
const getCanonicalFileName = createGetCanonicalFileName ( host . useCaseSensitiveFileNames ? host . useCaseSensitiveFileNames ( ) : true ) ;
155
160
const sourceDirectory = getDirectoryPath ( importingSourceFileName ) ;
156
- return { getCanonicalFileName, sourceDirectory } ;
161
+ return { getCanonicalFileName, importingSourceFileName , sourceDirectory } ;
157
162
}
158
163
159
- function getLocalModuleSpecifier ( moduleFileName : string , { getCanonicalFileName , sourceDirectory } : Info , compilerOptions : CompilerOptions , host : ModuleSpecifierResolutionHost , { ending, relativePreference } : Preferences ) : string {
164
+ function getLocalModuleSpecifier ( moduleFileName : string , info : Info , compilerOptions : CompilerOptions , host : ModuleSpecifierResolutionHost , { ending, relativePreference } : Preferences ) : string {
160
165
const { baseUrl, paths, rootDirs, bundledPackageName } = compilerOptions ;
166
+ const { sourceDirectory, getCanonicalFileName } = info ;
161
167
162
168
const relativePath = rootDirs && tryGetModuleNameFromRootDirs ( rootDirs , moduleFileName , sourceDirectory , getCanonicalFileName , ending , compilerOptions ) ||
163
169
removeExtensionAndIndexPostFix ( ensurePathIsNonModuleName ( getRelativePathFromDirectory ( sourceDirectory , moduleFileName , getCanonicalFileName ) ) , ending , compilerOptions ) ;
@@ -183,7 +189,42 @@ namespace ts.moduleSpecifiers {
183
189
return nonRelative ;
184
190
}
185
191
186
- if ( relativePreference !== RelativePreference . Auto ) Debug . assertNever ( relativePreference ) ;
192
+ if ( relativePreference === RelativePreference . ExternalNonRelative ) {
193
+ const projectDirectory = host . getCurrentDirectory ( ) ;
194
+ const modulePath = toPath ( moduleFileName , projectDirectory , getCanonicalFileName ) ;
195
+ const sourceIsInternal = startsWith ( sourceDirectory , projectDirectory ) ;
196
+ const targetIsInternal = startsWith ( modulePath , projectDirectory ) ;
197
+ if ( sourceIsInternal && ! targetIsInternal || ! sourceIsInternal && targetIsInternal ) {
198
+ // 1. The import path crosses the boundary of the tsconfig.json-containing directory.
199
+ //
200
+ // src/
201
+ // tsconfig.json
202
+ // index.ts -------
203
+ // lib/ | (path crosses tsconfig.json)
204
+ // imported.ts <---
205
+ //
206
+ return nonRelative ;
207
+ }
208
+
209
+ const nearestTargetPackageJson = getNearestAncestorDirectoryWithPackageJson ( host , getDirectoryPath ( modulePath ) ) ;
210
+ const nearestSourcePackageJson = getNearestAncestorDirectoryWithPackageJson ( host , sourceDirectory ) ;
211
+ if ( nearestSourcePackageJson !== nearestTargetPackageJson ) {
212
+ // 2. The importing and imported files are part of different packages.
213
+ //
214
+ // packages/a/
215
+ // package.json
216
+ // index.ts --------
217
+ // packages/b/ | (path crosses package.json)
218
+ // package.json |
219
+ // component.ts <---
220
+ //
221
+ return nonRelative ;
222
+ }
223
+
224
+ return relativePath ;
225
+ }
226
+
227
+ if ( relativePreference !== RelativePreference . Shortest ) Debug . assertNever ( relativePreference ) ;
187
228
188
229
// Prefer a relative import over a baseUrl import if it has fewer components.
189
230
return isPathRelativeToParent ( nonRelative ) || countPathComponents ( relativePath ) < countPathComponents ( nonRelative ) ? relativePath : nonRelative ;
@@ -213,6 +254,15 @@ namespace ts.moduleSpecifiers {
213
254
) ;
214
255
}
215
256
257
+ function getNearestAncestorDirectoryWithPackageJson ( host : ModuleSpecifierResolutionHost , fileName : string ) {
258
+ if ( host . getNearestAncestorDirectoryWithPackageJson ) {
259
+ return host . getNearestAncestorDirectoryWithPackageJson ( fileName ) ;
260
+ }
261
+ return ! ! forEachAncestorDirectory ( fileName , directory => {
262
+ return host . fileExists ( combinePaths ( directory , "package.json" ) ) ? true : undefined ;
263
+ } ) ;
264
+ }
265
+
216
266
export function forEachFileNameOfModule < T > (
217
267
importingFileName : string ,
218
268
importedFileName : string ,
0 commit comments