Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(language-service): force compileNonExportedClasses: false in LS #40092

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/language-service/ivy/language_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ export class LanguageService {
constructor(project: ts.server.Project, private readonly tsLS: ts.LanguageService) {
this.parseConfigHost = new LSParseConfigHost(project.projectService.host);
this.options = parseNgCompilerOptions(project, this.parseConfigHost);

// Projects loaded into the Language Service often include test files which are not part of the
// app's main compilation unit, and these test files often include inline NgModules that declare
// components from the app. These declarations conflict with the main declarations of such
// components in the app's NgModules. This conflict is not normally present during regular
// compilation because the app and the tests are part of separate compilation units.
//
// As a temporary mitigation of this problem, we instruct the compiler to ignore classes which
// are not exported. In many cases, this ensures the test NgModules are ignored by the compiler
// and only the real component declaration is used.
this.options.compileNonExportedClasses = false;

this.strategy = createTypeCheckingProgramStrategy(project);
this.adapter = new LanguageServiceAdapter(project);
this.compilerFactory = new CompilerFactory(this.adapter, this.strategy, this.options);
Expand Down
59 changes: 58 additions & 1 deletion packages/language-service/ivy/test/compiler_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {absoluteFrom} from '@angular/compiler-cli/src/ngtsc/file_system';
import {absoluteFrom, getSourceFileOrError} from '@angular/compiler-cli/src/ngtsc/file_system';
import {initMockFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing';

import {LanguageServiceTestEnvironment} from './env';
Expand All @@ -16,6 +16,63 @@ describe('language-service/compiler integration', () => {
initMockFileSystem('Native');
});

it('should not produce errors from inline test declarations mixing with those of the app', () => {
const appCmpFile = absoluteFrom('/test.cmp.ts');
const appModuleFile = absoluteFrom('/test.mod.ts');
const testFile = absoluteFrom('/test_spec.ts');

const env = LanguageServiceTestEnvironment.setup([
{
name: appCmpFile,
contents: `
import {Component} from '@angular/core';

@Component({
selector: 'app-cmp',
template: 'Some template',
})
export class AppCmp {}
`,
isRoot: true,
},
{
name: appModuleFile,
contents: `
import {NgModule} from '@angular/core';
import {AppCmp} from './test.cmp';

@NgModule({
declarations: [AppCmp],
})
export class AppModule {}
`,
isRoot: true,
},
{
name: testFile,
contents: `
import {NgModule} from '@angular/core';
import {AppCmp} from './test.cmp';

export function test(): void {
@NgModule({
declarations: [AppCmp],
})
class TestModule {}
}
`,
isRoot: true,
}
]);

// Expect that this program is clean diagnostically.
const ngCompiler = env.ngLS.compilerFactory.getOrCreate();
const program = ngCompiler.getNextProgram();
expect(ngCompiler.getDiagnostics(getSourceFileOrError(program, appCmpFile))).toEqual([]);
expect(ngCompiler.getDiagnostics(getSourceFileOrError(program, appModuleFile))).toEqual([]);
expect(ngCompiler.getDiagnostics(getSourceFileOrError(program, testFile))).toEqual([]);
});

it('should show type-checking errors from components with poisoned scopes', () => {
// Normally, the Angular compiler suppresses errors from components that belong to NgModules
// which themselves have errors (such scopes are considered "poisoned"), to avoid overwhelming
Expand Down