2016-11-22 09:10:23 -08:00
/ * *
* @license
* Copyright Google Inc . 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.io/license
* /
2017-05-09 16:16:50 -07:00
import { NgAnalyzedModules , StaticSymbol } from '@angular/compiler' ;
2019-08-09 15:52:49 -07:00
import { DiagnosticTemplateInfo , getTemplateExpressionDiagnostics } from '@angular/compiler-cli/src/language_services' ;
import * as ts from 'typescript' ;
2017-05-09 16:16:50 -07:00
import { AstResult } from './common' ;
2017-11-14 17:49:47 -08:00
import { Declarations , Diagnostic , DiagnosticKind , DiagnosticMessageChain , Diagnostics , Span , TemplateSource } from './types' ;
2019-08-09 15:52:49 -07:00
import { offsetSpan , spanOf } from './utils' ;
2016-11-22 09:10:23 -08:00
export interface AstProvider {
getTemplateAst ( template : TemplateSource , fileName : string ) : AstResult ;
}
2019-08-09 15:52:49 -07:00
export function getTemplateDiagnostics ( template : TemplateSource , ast : AstResult ) : Diagnostics {
const results : Diagnostics = [ ] ;
if ( ast . parseErrors && ast . parseErrors . length ) {
results . push ( . . . ast . parseErrors . map < Diagnostic > ( e = > {
return {
kind : DiagnosticKind.Error ,
span : offsetSpan ( spanOf ( e . span ) , template . span . start ) ,
message : e.msg ,
} ;
} ) ) ;
} else if ( ast . templateAst && ast . htmlAst ) {
const info : DiagnosticTemplateInfo = {
templateAst : ast.templateAst ,
htmlAst : ast.htmlAst ,
offset : template.span.start ,
query : template.query ,
members : template.members ,
} ;
const expressionDiagnostics = getTemplateExpressionDiagnostics ( info ) ;
results . push ( . . . expressionDiagnostics ) ;
}
if ( ast . errors ) {
results . push ( . . . ast . errors . map < Diagnostic > ( e = > {
return {
kind : e.kind ,
span : e.span || template . span ,
message : e.message ,
} ;
} ) ) ;
}
return results ;
}
2016-11-22 09:10:23 -08:00
export function getDeclarationDiagnostics (
declarations : Declarations , modules : NgAnalyzedModules ) : Diagnostics {
const results : Diagnostics = [ ] ;
let directives : Set < StaticSymbol > | undefined = undefined ;
for ( const declaration of declarations ) {
2017-11-14 17:49:47 -08:00
const report = ( message : string | DiagnosticMessageChain , span? : Span ) = > {
2016-12-02 14:34:16 -08:00
results . push ( < Diagnostic > {
kind : DiagnosticKind.Error ,
span : span || declaration . declarationSpan , message
} ) ;
2016-11-22 09:10:23 -08:00
} ;
2016-12-02 14:34:16 -08:00
for ( const error of declaration . errors ) {
report ( error . message , error . span ) ;
2016-11-22 09:10:23 -08:00
}
if ( declaration . metadata ) {
if ( declaration . metadata . isComponent ) {
if ( ! modules . ngModuleByPipeOrDirective . has ( declaration . type ) ) {
report (
` Component ' ${ declaration . type . name } ' is not included in a module and will not be available inside a template. Consider adding it to a NgModule declaration ` ) ;
}
2017-11-22 12:09:49 -08:00
const { template , templateUrl } = declaration . metadata . template ! ;
if ( template === null && ! templateUrl ) {
report ( ` Component ' ${ declaration . type . name } ' must have a template or templateUrl ` ) ;
} else if ( template && templateUrl ) {
report (
` Component ' ${ declaration . type . name } ' must not have both template and templateUrl ` ) ;
2016-11-22 09:10:23 -08:00
}
} else {
if ( ! directives ) {
directives = new Set ( ) ;
modules . ngModules . forEach ( module = > {
module . declaredDirectives . forEach (
2017-03-24 09:57:32 -07:00
directive = > { directives ! . add ( directive . reference ) ; } ) ;
2016-11-22 09:10:23 -08:00
} ) ;
}
if ( ! directives . has ( declaration . type ) ) {
report (
` Directive ' ${ declaration . type . name } ' is not included in a module and will not be available inside a template. Consider adding it to a NgModule declaration ` ) ;
}
}
}
}
return results ;
}
2019-08-09 15:52:49 -07:00
function diagnosticChainToDiagnosticChain ( chain : DiagnosticMessageChain ) :
ts . DiagnosticMessageChain {
return {
messageText : chain.message ,
category : ts.DiagnosticCategory.Error ,
code : 0 ,
next : chain.next ? diagnosticChainToDiagnosticChain ( chain . next ) : undefined
} ;
}
function diagnosticMessageToDiagnosticMessageText ( message : string | DiagnosticMessageChain ) : string |
ts . DiagnosticMessageChain {
if ( typeof message === 'string' ) {
return message ;
}
return diagnosticChainToDiagnosticChain ( message ) ;
}
export function ngDiagnosticToTsDiagnostic (
d : Diagnostic , file : ts.SourceFile | undefined ) : ts . Diagnostic {
return {
file ,
start : d.span.start ,
length : d.span.end - d . span . start ,
messageText : diagnosticMessageToDiagnosticMessageText ( d . message ) ,
category : ts.DiagnosticCategory.Error ,
code : 0 ,
source : 'ng' ,
} ;
}
2019-08-12 16:54:36 -07:00
export function uniqueBySpan < T extends { span : Span } > ( elements : T [ ] ) : T [ ] {
const result : T [ ] = [ ] ;
const map = new Map < number , Set < number > > ( ) ;
for ( const element of elements ) {
const { span } = element ;
let set = map . get ( span . start ) ;
if ( ! set ) {
set = new Set ( ) ;
map . set ( span . start , set ) ;
}
if ( ! set . has ( span . end ) ) {
set . add ( span . end ) ;
result . push ( element ) ;
}
}
return result ;
}