mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
By default, the compiler-cli uses the relative import strategy when there is no `rootDir` or `rootDirs`. This is expected as everything is assumed to be somehow reachable through relative imports. With `rootDirs` that allow for a "virtual file system"-like environment, the compiler is not necessarily able to always construct proper relative imports. The compiler includes the `LogicalProjectStrategy` for this reason. This strategy is able to respect `rootDirs` to construct relative paths when possible. This logic currently accidentally triggers when there is a `rootDir` set. This option is not to be confused with the virtual directory option called `rootDirs`. The compiler currently confuses this and accidentally enters this mode when there is just a `rootDir`— breaking in monorepos that imports can point outside the `rootDir` to e.g. other compilation unit's `.d.ts` (which is valid; just not `.ts` sources can live outside the root dir). This is necessary for our Bazel toolchain migration. PR Close #60555
180 lines
5.2 KiB
TypeScript
180 lines
5.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
import ts from 'typescript';
|
|
|
|
import {absoluteFrom} from '../../src/ngtsc/file_system';
|
|
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
|
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
|
|
|
import {NgtscTestEnvironment} from './env';
|
|
|
|
const testFiles = loadStandardTestFiles();
|
|
|
|
runInEachFileSystem(() => {
|
|
describe('import generation', () => {
|
|
let env!: NgtscTestEnvironment;
|
|
|
|
beforeEach(() => {
|
|
env = NgtscTestEnvironment.setup(testFiles);
|
|
const tsconfig: {[key: string]: any} = {
|
|
extends: '../tsconfig-base.json',
|
|
compilerOptions: {
|
|
baseUrl: '.',
|
|
rootDirs: ['/app'],
|
|
},
|
|
angularCompilerOptions: {},
|
|
};
|
|
env.write('tsconfig.json', JSON.stringify(tsconfig, null, 2));
|
|
});
|
|
|
|
it('should report an error when using a directive outside of rootDirs', () => {
|
|
env.write(
|
|
'/app/module.ts',
|
|
`
|
|
import {NgModule} from '@angular/core';
|
|
import {ExternalDir} from '../lib/dir';
|
|
import {MyComponent} from './comp';
|
|
|
|
@NgModule({
|
|
declarations: [ExternalDir, MyComponent],
|
|
})
|
|
export class MyModule {}
|
|
`,
|
|
);
|
|
env.write(
|
|
'/app/comp.ts',
|
|
`
|
|
import {Component} from '@angular/core';
|
|
|
|
@Component({
|
|
template: '<div external></div>',
|
|
})
|
|
export class MyComponent {}
|
|
`,
|
|
);
|
|
env.write(
|
|
'/lib/dir.ts',
|
|
`
|
|
import {Directive} from '@angular/core';
|
|
|
|
@Directive({selector: '[external]'})
|
|
export class ExternalDir {}
|
|
`,
|
|
);
|
|
|
|
const diags = env.driveDiagnostics();
|
|
expect(diags.length).toBe(1);
|
|
expect(ts.flattenDiagnosticMessageText(diags[0].messageText, '\n'))
|
|
.toEqual(`Unable to import class ExternalDir.
|
|
The file ${absoluteFrom('/lib/dir.ts')} is outside of the configured 'rootDir'.`);
|
|
expect(diags[0].file!.fileName).toEqual(absoluteFrom('/app/module.ts'));
|
|
expect(getDiagnosticSourceCode(diags[0])).toEqual('ExternalDir');
|
|
});
|
|
|
|
it('should report an error when a library entry-point does not export the symbol', () => {
|
|
env.write(
|
|
'/app/module.ts',
|
|
`
|
|
import {NgModule} from '@angular/core';
|
|
import {ExternalModule} from 'lib';
|
|
import {MyComponent} from './comp';
|
|
|
|
@NgModule({
|
|
imports: [ExternalModule],
|
|
declarations: [MyComponent],
|
|
})
|
|
export class MyModule {}
|
|
`,
|
|
);
|
|
env.write(
|
|
'/app/comp.ts',
|
|
`
|
|
import {Component} from '@angular/core';
|
|
|
|
@Component({
|
|
template: '<div external></div>',
|
|
standalone: false,
|
|
})
|
|
export class MyComponent {}
|
|
`,
|
|
);
|
|
env.write(
|
|
'/node_modules/lib/index.d.ts',
|
|
`
|
|
import {ɵɵNgModuleDeclaration} from '@angular/core';
|
|
import {ExternalDir} from './dir';
|
|
|
|
export class ExternalModule {
|
|
static ɵmod: ɵɵNgModuleDeclaration<ExternalModule, [typeof ExternalDir], never, [typeof ExternalDir]>;
|
|
}
|
|
`,
|
|
);
|
|
env.write(
|
|
'/node_modules/lib/dir.d.ts',
|
|
`
|
|
import {ɵɵDirectiveDeclaration} from '@angular/core';
|
|
|
|
export class ExternalDir {
|
|
static ɵdir: ɵɵDirectiveDeclaration<ExternalDir, '[external]', never, never, never, never>;
|
|
}
|
|
`,
|
|
);
|
|
|
|
const diags = env.driveDiagnostics();
|
|
expect(diags.length).toBe(1);
|
|
expect(ts.flattenDiagnosticMessageText(diags[0].messageText, '\n'))
|
|
.toEqual(`Unable to import directive ExternalDir.
|
|
The symbol is not exported from ${absoluteFrom('/node_modules/lib/index.d.ts')} (module 'lib').`);
|
|
expect(diags[0].file!.fileName).toEqual(absoluteFrom('/app/comp.ts'));
|
|
expect(getDiagnosticSourceCode(diags[0])).toEqual('MyComponent');
|
|
});
|
|
|
|
it('should be possible to use a directive outside of `rootDir` when no `rootDirs` are set.', () => {
|
|
env.write(
|
|
'tsconfig.json',
|
|
JSON.stringify(
|
|
{
|
|
extends: './tsconfig-base.json',
|
|
compilerOptions: {rootDir: './app'},
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
env.write(
|
|
'/app/module.ts',
|
|
`
|
|
import {NgModule} from '@angular/core';
|
|
import {ExternalDir} from '../lib/dir';
|
|
|
|
@NgModule({
|
|
imports: [ExternalDir],
|
|
})
|
|
export class MyModule {}
|
|
`,
|
|
);
|
|
env.write(
|
|
'/lib/dir.d.ts',
|
|
`
|
|
import {ɵɵDirectiveDeclaration} from '@angular/core';
|
|
|
|
export class ExternalDir {
|
|
static ɵdir: ɵɵDirectiveDeclaration<ExternalDir, '[external]', never, never, never, never, never, true>;
|
|
}
|
|
`,
|
|
);
|
|
|
|
const diags = env.driveDiagnostics();
|
|
expect(diags.length).toBe(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
function getDiagnosticSourceCode(diag: ts.Diagnostic): string {
|
|
return diag.file!.text.substring(diag.start!, diag.start! + diag.length!);
|
|
}
|