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(ivy): provide a more detailed error message for NG6002/NG6003 #35620

Closed
wants to merge 1 commit into from
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
29 changes: 26 additions & 3 deletions packages/compiler-cli/src/ngtsc/scope/src/local.ts
Expand Up @@ -551,9 +551,32 @@ function invalidRef(
const code =
type === 'import' ? ErrorCode.NGMODULE_INVALID_IMPORT : ErrorCode.NGMODULE_INVALID_EXPORT;
const resolveTarget = type === 'import' ? 'NgModule' : 'NgModule, Component, Directive, or Pipe';
return makeDiagnostic(
code, identifierOfNode(decl.node) || decl.node,
`Appears in the NgModule.${type}s of ${nodeNameForError(clazz)}, but could not be resolved to an ${resolveTarget} class`);
let message =
`Appears in the NgModule.${type}s of ${nodeNameForError(clazz)}, but could not be resolved to an ${resolveTarget} class.` +
'\n\n';
const library = decl.ownedByModuleGuess !== null ? ` (${decl.ownedByModuleGuess})` : '';
const sf = decl.node.getSourceFile();

// Provide extra context to the error for the user.
if (!sf.isDeclarationFile) {
// This is a file in the user's program.
const annotationType = type === 'import' ? '@NgModule' : 'Angular';
message += `Is it missing an ${annotationType} annotation?`;
} else if (sf.fileName.indexOf('node_modules') !== -1) {
// This file comes from a third-party library in node_modules.
message +=
`This likely means that the library${library} which declares ${decl.debugName} has not ` +
'been processed correctly by ngcc, or is not compatible with Angular Ivy. Check if a ' +
'newer version of the library is available, and update if so. Also consider checking ' +
'with the library\'s authors to see if the library is expected to be compatible with Ivy.';
} else {
// This is a monorepo style local dependency. Unfortunately these are too different to really
// offer much more advice than this.
message +=
`This likely means that the dependency${library} which declares ${decl.debugName} has not been processed correctly by ngcc.`;
}

return makeDiagnostic(code, identifierOfNode(decl.node) || decl.node, message);
}

/**
Expand Down
70 changes: 70 additions & 0 deletions packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Expand Up @@ -3769,6 +3769,76 @@ runInEachFileSystem(os => {
// Success is enough to indicate that this passes.
});

describe('NgModule invalid import/export errors', () => {
function verifyThrownError(errorCode: ErrorCode, errorMessage: string) {
const errors = env.driveDiagnostics();
expect(errors.length).toBe(1);
const {code, messageText} = errors[0];
expect(code).toBe(ngErrorCode(errorCode));
expect(trim(messageText as string)).toContain(errorMessage);
}

it('should provide a hint when importing an invalid NgModule from node_modules', () => {
env.write('node_modules/external/index.d.ts', `
export declare class NotAModule {}
`);
env.write('test.ts', `
import {NgModule} from '@angular/core';
import {NotAModule} from 'external';

@NgModule({
imports: [NotAModule],
})
export class Module {}
`);

verifyThrownError(
ErrorCode.NGMODULE_INVALID_IMPORT,
'This likely means that the library (external) which declares NotAModule has not ' +
'been processed correctly by ngcc, or is not compatible with Angular Ivy.');
});

it('should provide a hint when importing an invalid NgModule from a local library', () => {
env.write('libs/external/index.d.ts', `
export declare class NotAModule {}
`);

env.write('test.ts', `
import {NgModule} from '@angular/core';
import {NotAModule} from './libs/external';

@NgModule({
imports: [NotAModule],
})
export class Module {}
`);

verifyThrownError(
ErrorCode.NGMODULE_INVALID_IMPORT,
'This likely means that the dependency which declares NotAModule has not ' +
'been processed correctly by ngcc.');
});

it('should provide a hint when importing an invalid NgModule in the current program', () => {
env.write('invalid.ts', `
export class NotAModule {}
`);

env.write('test.ts', `
import {NgModule} from '@angular/core';
import {NotAModule} from './invalid';

@NgModule({
imports: [NotAModule],
})
export class Module {}
`);

verifyThrownError(
ErrorCode.NGMODULE_INVALID_IMPORT, 'Is it missing an @NgModule annotation?');
});
});

describe('when processing external directives', () => {
it('should not emit multiple references to the same directive', () => {
env.write('node_modules/external/index.d.ts', `
Expand Down