2018-04-24 19:48:55 +02:00
/ * *
* Test harness compiler functionality .
* /
2017-11-10 03:35:24 +01:00
namespace compiler {
2017-11-10 01:01:33 +01:00
export interface Project {
file : string ;
config? : ts.ParsedCommandLine ;
errors? : ts.Diagnostic [ ] ;
}
2018-04-19 20:30:03 +02:00
export function readProject ( host : fakes.ParseConfigHost , project : string | undefined , existingOptions? : ts.CompilerOptions ) : Project | undefined {
2017-11-10 01:01:33 +01:00
if ( project ) {
2018-05-09 02:07:54 +02:00
project = vpath . isTsConfigFile ( project ) ? project : vpath.combine ( project , "tsconfig.json" ) ;
2017-11-10 01:01:33 +01:00
}
else {
2018-01-23 19:27:20 +01:00
[ project ] = host . vfs . scanSync ( "." , "ancestors-or-self" , {
2018-01-23 19:40:56 +01:00
accept : ( path , stats ) = > stats . isFile ( ) && host . vfs . stringComparer ( vpath . basename ( path ) , "tsconfig.json" ) === 0
2018-01-23 19:27:20 +01:00
} ) ;
2017-11-10 01:01:33 +01:00
}
if ( project ) {
// TODO(rbuckton): Do we need to resolve this? Resolving breaks projects tests.
// project = vpath.resolve(host.vfs.currentDirectory, project);
// read the config file
const readResult = ts . readConfigFile ( project , path = > host . readFile ( path ) ) ;
if ( readResult . error ) {
return { file : project , errors : [ readResult . error ] } ;
}
// parse the config file
const config = ts . parseJsonConfigFileContent ( readResult . config , host , vpath . dirname ( project ) , existingOptions ) ;
return { file : project , errors : config.errors , config } ;
}
}
2017-12-21 20:20:44 +01:00
/ * *
* Correlates compilation inputs and outputs
* /
2017-11-10 01:01:33 +01:00
export interface CompilationOutput {
2019-08-08 20:30:18 +02:00
readonly inputs : readonly documents . TextDocument [ ] ;
2017-11-10 03:35:24 +01:00
readonly js : documents.TextDocument | undefined ;
readonly dts : documents.TextDocument | undefined ;
readonly map : documents.TextDocument | undefined ;
2017-11-10 01:01:33 +01:00
}
export class CompilationResult {
2018-04-19 20:30:03 +02:00
public readonly host : fakes.CompilerHost ;
2017-11-10 01:01:33 +01:00
public readonly program : ts.Program | undefined ;
public readonly result : ts.EmitResult | undefined ;
public readonly options : ts.CompilerOptions ;
2019-08-08 20:30:18 +02:00
public readonly diagnostics : readonly ts . Diagnostic [ ] ;
2018-01-30 20:57:47 +01:00
public readonly js : ReadonlyMap < string , documents.TextDocument > ;
public readonly dts : ReadonlyMap < string , documents.TextDocument > ;
public readonly maps : ReadonlyMap < string , documents.TextDocument > ;
2018-07-24 22:56:21 +02:00
public symlinks? : vfs.FileSet ; // Location to store original symlinks so they may be used in both original and declaration file compilations
2017-11-10 01:01:33 +01:00
2017-11-10 03:35:24 +01:00
private _inputs : documents.TextDocument [ ] = [ ] ;
2018-04-24 19:48:55 +02:00
private _inputsAndOutputs : collections.SortedMap < string , CompilationOutput > ;
2017-11-10 01:01:33 +01:00
2019-08-08 20:30:18 +02:00
constructor ( host : fakes.CompilerHost , options : ts.CompilerOptions , program : ts.Program | undefined , result : ts.EmitResult | undefined , diagnostics : readonly ts . Diagnostic [ ] ) {
2017-11-10 01:01:33 +01:00
this . host = host ;
this . program = program ;
this . result = result ;
this . diagnostics = diagnostics ;
this . options = program ? program . getCompilerOptions ( ) : options ;
// collect outputs
2018-04-24 19:48:55 +02:00
const js = this . js = new collections . SortedMap < string , documents.TextDocument > ( { comparer : this.vfs.stringComparer , sort : "insertion" } ) ;
const dts = this . dts = new collections . SortedMap < string , documents.TextDocument > ( { comparer : this.vfs.stringComparer , sort : "insertion" } ) ;
const maps = this . maps = new collections . SortedMap < string , documents.TextDocument > ( { comparer : this.vfs.stringComparer , sort : "insertion" } ) ;
2017-11-10 01:01:33 +01:00
for ( const document of this . host . outputs ) {
2018-05-04 00:35:54 +02:00
if ( vpath . isJavaScript ( document . file ) || ts . fileExtensionIs ( document . file , ts . Extension . Json ) ) {
2017-11-10 22:52:12 +01:00
js . set ( document . file , document ) ;
2017-11-10 01:01:33 +01:00
}
2018-04-20 01:47:07 +02:00
else if ( vpath . isDeclaration ( document . file ) ) {
2017-11-10 22:52:12 +01:00
dts . set ( document . file , document ) ;
2017-11-10 01:01:33 +01:00
}
2018-04-20 01:47:07 +02:00
else if ( vpath . isSourceMap ( document . file ) ) {
2017-11-10 22:52:12 +01:00
maps . set ( document . file , document ) ;
2017-11-10 01:01:33 +01:00
}
}
// correlate inputs and outputs
2018-04-24 19:48:55 +02:00
this . _inputsAndOutputs = new collections . SortedMap < string , CompilationOutput > ( { comparer : this.vfs.stringComparer , sort : "insertion" } ) ;
2017-11-10 01:01:33 +01:00
if ( program ) {
2017-12-21 20:20:44 +01:00
if ( this . options . out || this . options . outFile ) {
2018-01-20 06:58:46 +01:00
const outFile = vpath . resolve ( this . vfs . cwd ( ) , this . options . outFile || this . options . out ) ;
2017-12-21 20:20:44 +01:00
const inputs : documents.TextDocument [ ] = [ ] ;
for ( const sourceFile of program . getSourceFiles ( ) ) {
if ( sourceFile ) {
const input = new documents . TextDocument ( sourceFile . fileName , sourceFile . text ) ;
this . _inputs . push ( input ) ;
2018-04-20 01:47:07 +02:00
if ( ! vpath . isDeclaration ( sourceFile . fileName ) ) {
2017-12-21 20:20:44 +01:00
inputs . push ( input ) ;
}
}
}
const outputs : CompilationOutput = {
inputs ,
js : js.get ( outFile ) ,
dts : dts.get ( vpath . changeExtension ( outFile , ".d.ts" ) ) ,
map : maps.get ( outFile + ".map" )
} ;
if ( outputs . js ) this . _inputsAndOutputs . set ( outputs . js . file , outputs ) ;
if ( outputs . dts ) this . _inputsAndOutputs . set ( outputs . dts . file , outputs ) ;
if ( outputs . map ) this . _inputsAndOutputs . set ( outputs . map . file , outputs ) ;
for ( const input of inputs ) {
this . _inputsAndOutputs . set ( input . file , outputs ) ;
}
}
else {
for ( const sourceFile of program . getSourceFiles ( ) ) {
if ( sourceFile ) {
const input = new documents . TextDocument ( sourceFile . fileName , sourceFile . text ) ;
this . _inputs . push ( input ) ;
2018-04-20 01:47:07 +02:00
if ( ! vpath . isDeclaration ( sourceFile . fileName ) ) {
2021-09-24 23:25:59 +02:00
const extname = ts . getOutputExtension ( sourceFile . fileName , this . options ) ;
2017-12-21 20:20:44 +01:00
const outputs : CompilationOutput = {
inputs : [ input ] ,
js : js.get ( this . getOutputPath ( sourceFile . fileName , extname ) ) ,
2021-09-24 23:25:59 +02:00
dts : dts.get ( this . getOutputPath ( sourceFile . fileName , ts . getDeclarationEmitExtensionForPath ( sourceFile . fileName ) ) ) ,
2017-12-21 20:20:44 +01:00
map : maps.get ( this . getOutputPath ( sourceFile . fileName , extname + ".map" ) )
} ;
this . _inputsAndOutputs . set ( sourceFile . fileName , outputs ) ;
if ( outputs . js ) this . _inputsAndOutputs . set ( outputs . js . file , outputs ) ;
if ( outputs . dts ) this . _inputsAndOutputs . set ( outputs . dts . file , outputs ) ;
if ( outputs . map ) this . _inputsAndOutputs . set ( outputs . map . file , outputs ) ;
}
2017-11-10 01:01:33 +01:00
}
}
}
}
}
2018-01-20 06:58:46 +01:00
public get vfs ( ) : vfs . FileSystem {
2017-11-10 01:01:33 +01:00
return this . host . vfs ;
}
2019-08-08 20:30:18 +02:00
public get inputs ( ) : readonly documents . TextDocument [ ] {
2017-11-10 01:01:33 +01:00
return this . _inputs ;
}
2019-08-08 20:30:18 +02:00
public get outputs ( ) : readonly documents . TextDocument [ ] {
2017-11-10 01:01:33 +01:00
return this . host . outputs ;
}
2019-08-08 20:30:18 +02:00
public get traces ( ) : readonly string [ ] {
2017-11-10 01:01:33 +01:00
return this . host . traces ;
}
public get emitSkipped ( ) : boolean {
return this . result && this . result . emitSkipped || false ;
}
public get singleFile ( ) : boolean {
return ! ! this . options . outFile || ! ! this . options . out ;
}
public get commonSourceDirectory ( ) : string {
const common = this . program && this . program . getCommonSourceDirectory ( ) || "" ;
2018-01-20 06:58:46 +01:00
return common && vpath . combine ( this . vfs . cwd ( ) , common ) ;
2017-11-10 01:01:33 +01:00
}
public getInputsAndOutputs ( path : string ) : CompilationOutput | undefined {
2018-01-20 06:58:46 +01:00
return this . _inputsAndOutputs . get ( vpath . resolve ( this . vfs . cwd ( ) , path ) ) ;
2017-11-10 01:01:33 +01:00
}
2019-08-08 20:30:18 +02:00
public getInputs ( path : string ) : readonly documents . TextDocument [ ] | undefined {
2017-11-10 01:01:33 +01:00
const outputs = this . getInputsAndOutputs ( path ) ;
2017-12-21 20:20:44 +01:00
return outputs && outputs . inputs ;
2017-11-10 01:01:33 +01:00
}
2017-11-10 03:35:24 +01:00
public getOutput ( path : string , kind : "js" | "dts" | "map" ) : documents . TextDocument | undefined {
2017-11-10 01:01:33 +01:00
const outputs = this . getInputsAndOutputs ( path ) ;
return outputs && outputs [ kind ] ;
}
2017-11-10 22:52:12 +01:00
public getSourceMapRecord ( ) : string | undefined {
2019-01-11 23:24:49 +01:00
const maps = this . result ! . sourceMaps ;
if ( maps && maps . length > 0 ) {
return Harness . SourceMapRecorder . getSourceMapRecord ( maps , this . program ! , Array . from ( this . js . values ( ) ) . filter ( d = > ! ts . fileExtensionIs ( d . file , ts . Extension . Json ) ) , Array . from ( this . dts . values ( ) ) ) ;
2017-11-10 22:52:12 +01:00
}
}
2017-11-10 03:35:24 +01:00
public getSourceMap ( path : string ) : documents . SourceMap | undefined {
2018-04-20 01:47:07 +02:00
if ( this . options . noEmit || vpath . isDeclaration ( path ) ) return undefined ;
2017-11-10 01:01:33 +01:00
if ( this . options . inlineSourceMap ) {
const document = this . getOutput ( path , "js" ) ;
2017-11-10 03:35:24 +01:00
return document && documents . SourceMap . fromSource ( document . text ) ;
2017-11-10 01:01:33 +01:00
}
if ( this . options . sourceMap ) {
const document = this . getOutput ( path , "map" ) ;
2017-11-10 03:35:24 +01:00
return document && new documents . SourceMap ( document . file , document . text ) ;
2017-11-10 01:01:33 +01:00
}
}
2017-12-21 20:20:44 +01:00
public getOutputPath ( path : string , ext : string ) : string {
if ( this . options . outFile || this . options . out ) {
2018-01-20 06:58:46 +01:00
path = vpath . resolve ( this . vfs . cwd ( ) , this . options . outFile || this . options . out ) ;
2017-12-21 20:20:44 +01:00
}
else {
2018-01-20 06:58:46 +01:00
path = vpath . resolve ( this . vfs . cwd ( ) , path ) ;
2021-09-24 23:25:59 +02:00
const outDir = ext === ".d.ts" || ext === ".json.d.ts" || ext === ".d.mts" || ext === ".d.cts" ? this . options . declarationDir || this . options.outDir : this.options.outDir ;
2017-12-21 20:20:44 +01:00
if ( outDir ) {
const common = this . commonSourceDirectory ;
if ( common ) {
2018-01-20 06:58:46 +01:00
path = vpath . relative ( common , path , this . vfs . ignoreCase ) ;
path = vpath . combine ( vpath . resolve ( this . vfs . cwd ( ) , this . options . outDir ) , path ) ;
2017-12-21 20:20:44 +01:00
}
}
2017-11-10 01:01:33 +01:00
}
2017-12-21 20:20:44 +01:00
return vpath . changeExtension ( path , ext ) ;
2017-11-10 01:01:33 +01:00
}
2018-06-02 02:42:09 +02:00
Allow `allowJs` and `declaration` to be used together (#32372)
* Allow allowJs and declaration to be used together
This intorduces a new symbol-based declaration emitter - currently this
is only used for JSON and JavaScript, as the output is likely worse than
what the other declaration emitter is capable of. In addition, it is
still incomplete - it does not yet support serializaing namespaces.
* Add tests for various import/export forms, add notes on export as namespace and fix export * from
* Tests & fixes for computed names
* Add test with current @enum tag behavior
* fix declaration emit for jsdoc @enum tags
* Small adjustments to base class serialization to fix bugs in it
* Guard against type/type parameter confusion when using typeParameterToName a bit
* Integrate feedback from PR
* Fix issue with export= declarations visibility calculation and type declaration emit that impacted all forms of declaration emit
* Only make one merged getCommonJsExportEquals symbol for a symbol
* Support preserving type reference directives in js declarations
* Skip declare mdoifiers for namespace members in ambient contexts
* FAKE ALIASES AND NAMESPACES EVERYWHERE
* Dont do namespace sugar when type members contain keyword names
* Fix json source file export modifier under new output
* Such clean nested aliasing, very wow
* Fix lint
* Add visibility errors, reuse type nodes where possible
* Suppoer having correctly named import types in bundled js declaration emit & adjust binding to allow namespaces with aliases to merge when the aliases look to be type-only
* Better support for module.exports = class expression
* Fix discovered crash bug
* Allow export assigned class expressions to be reachable symbols from external declarations
* Add missing semicolon
* Support @enum tag post-merge
* preserve comments on signatures and declarations where possible
* Basic support for js classy functions
* Add example we should do better with
* Prototype assignments make things a bit wonky, but the example from the PR seems OK
* Make a ton of changes to support the new way js classes are bound
* Remove some old comments, fix import and export default names
* Fix bug in object define handling and add tests for object define property declaration emit
* Fix organization nits from PR comments
* Preserve comments from jsdoc declarations on properties and js declaration type aliases
* Merge export declarations with identical specifiers
* Remove completed TODO comment
* Split lint
* Remove now-unused function
* PR feedback
* Add some project references tests, remove some checks from project refs codepaths that are now invalid
* Update project references tests again
* Merge and update project references tests
* Rename case
* Update test to include declaration output
* Remove yet another project refernces redirect extension check
* Update comment
* Add additional import ref to test
* Add shorthand prop to test
* Fix comment text
* Extract var to temp
* Simplify function and add whitespace
* Update project refs test to use incremental edit entry
* Stylistic refactors in the symbol serializer
* Another round of PR feedback, mostly style, small bugfix with constructors, and test showing bug in export assigned class expression name shadowing
* Use x instead of index
2019-09-26 23:27:16 +02:00
public getNumberOfJsFiles ( includeJson : boolean ) {
if ( includeJson ) {
return this . js . size ;
}
else {
let count = this . js . size ;
this . js . forEach ( document = > {
if ( ts . fileExtensionIs ( document . file , ts . Extension . Json ) ) {
count -- ;
}
} ) ;
return count ;
}
2018-06-02 02:42:09 +02:00
}
2017-11-10 01:01:33 +01:00
}
2018-04-19 20:30:03 +02:00
export function compileFiles ( host : fakes.CompilerHost , rootFiles : string [ ] | undefined , compilerOptions : ts.CompilerOptions ) : CompilationResult {
2017-11-10 01:01:33 +01:00
if ( compilerOptions . project || ! rootFiles || rootFiles . length === 0 ) {
const project = readProject ( host . parseConfigHost , compilerOptions . project , compilerOptions ) ;
if ( project ) {
if ( project . errors && project . errors . length > 0 ) {
return new CompilationResult ( host , compilerOptions , /*program*/ undefined , /*result*/ undefined , project . errors ) ;
}
if ( project . config ) {
rootFiles = project . config . fileNames ;
compilerOptions = project . config . options ;
}
}
delete compilerOptions . project ;
}
2017-11-10 22:52:12 +01:00
// establish defaults (aligns with old harness)
2021-09-24 23:25:59 +02:00
if ( compilerOptions . target === undefined && compilerOptions . module !== ts . ModuleKind . Node12 && compilerOptions . module !== ts . ModuleKind . NodeNext ) compilerOptions . target = ts . ScriptTarget . ES3 ;
2017-11-10 01:01:33 +01:00
if ( compilerOptions . newLine === undefined ) compilerOptions . newLine = ts . NewLineKind . CarriageReturnLineFeed ;
if ( compilerOptions . skipDefaultLibCheck === undefined ) compilerOptions . skipDefaultLibCheck = true ;
if ( compilerOptions . noErrorTruncation === undefined ) compilerOptions . noErrorTruncation = true ;
2021-05-12 21:11:20 +02:00
// pre-emit/post-emit error comparison requires declaration emit twice, which can be slow. If it's unlikely to flag any error consistency issues
// and if the test is running `skipLibCheck` - an indicator that we want the tets to run quickly - skip the before/after error comparison, too
const skipErrorComparison = ts . length ( rootFiles ) >= 100 || ( ! ! compilerOptions . skipLibCheck && ! ! compilerOptions . declaration ) ;
const preProgram = ! skipErrorComparison ? ts . createProgram ( rootFiles || [ ] , { . . . compilerOptions , configFile : compilerOptions.configFile , traceResolution : false } , host ) : undefined ;
2020-08-27 09:44:18 +02:00
const preErrors = preProgram && ts . getPreEmitDiagnostics ( preProgram ) ;
2020-08-18 00:30:19 +02:00
2017-11-10 01:01:33 +01:00
const program = ts . createProgram ( rootFiles || [ ] , compilerOptions , host ) ;
const emitResult = program . emit ( ) ;
2020-08-18 00:30:19 +02:00
const postErrors = ts . getPreEmitDiagnostics ( program ) ;
2021-09-24 23:25:59 +02:00
const longerErrors = ts . length ( preErrors ) > postErrors . length ? preErrors : postErrors ;
const shorterErrors = longerErrors === preErrors ? postErrors : preErrors ;
const errors = preErrors && ( preErrors . length !== postErrors . length ) ? [ . . . shorterErrors ! ,
2020-08-18 00:30:19 +02:00
ts . addRelatedInfo (
ts . createCompilerDiagnostic ( {
category : ts.DiagnosticCategory.Error ,
code : - 1 ,
key : "-1" ,
message : ` Pre-emit ( ${ preErrors . length } ) and post-emit ( ${ postErrors . length } ) diagnostic counts do not match! This can indicate that a semantic _error_ was added by the emit resolver - such an error may not be reflected on the command line or in the editor, but may be captured in a baseline here! `
} ) ,
ts . createCompilerDiagnostic ( {
category : ts.DiagnosticCategory.Error ,
code : - 1 ,
key : "-1" ,
message : ` The excess diagnostics are: `
} ) ,
2021-09-24 23:25:59 +02:00
. . . ts . filter ( longerErrors ! , p = > ! ts . some ( shorterErrors , p2 = > ts . compareDiagnostics ( p , p2 ) === ts . Comparison . EqualTo ) )
2020-08-18 00:30:19 +02:00
)
] : postErrors ;
2017-11-10 01:01:33 +01:00
return new CompilationResult ( host , compilerOptions , program , emitResult , errors ) ;
}
2018-05-04 00:35:54 +02:00
}