@@ -27,11 +27,18 @@ namespace ts.tscWatch {
27
27
return ( ) => watch . getCurrentProgram ( ) ;
28
28
}
29
29
30
+ interface Watch {
31
+ ( ) : Program ;
32
+ getBuilderProgram ( ) : EmitAndSemanticDiagnosticsBuilderProgram ;
33
+ }
34
+
30
35
export function createWatchOfConfigFile ( configFileName : string , host : WatchedSystem , maxNumberOfFilesToIterateForInvalidation ?: number ) {
31
36
const compilerHost = createWatchCompilerHostOfConfigFile ( configFileName , { } , host ) ;
32
37
compilerHost . maxNumberOfFilesToIterateForInvalidation = maxNumberOfFilesToIterateForInvalidation ;
33
38
const watch = createWatchProgram ( compilerHost ) ;
34
- return ( ) => watch . getCurrentProgram ( ) . getProgram ( ) ;
39
+ const result = ( ( ) => watch . getCurrentProgram ( ) . getProgram ( ) ) as Watch ;
40
+ result . getBuilderProgram = ( ) => watch . getCurrentProgram ( ) ;
41
+ return result ;
35
42
}
36
43
37
44
function createWatchOfFilesAndCompilerOptions ( rootFiles : string [ ] , host : WatchedSystem , options : CompilerOptions = { } , maxNumberOfFilesToIterateForInvalidation ?: number ) {
@@ -182,7 +189,22 @@ namespace ts.tscWatch {
182
189
assert . equal ( host . exitCode , expectedExitCode ) ;
183
190
}
184
191
185
- function getDiagnosticOfFileFrom ( file : SourceFile | undefined , text : string , start : number | undefined , length : number | undefined , message : DiagnosticMessage ) : Diagnostic {
192
+ function isDiagnosticMessageChain ( message : DiagnosticMessage | DiagnosticMessageChain ) : message is DiagnosticMessageChain {
193
+ return ! ! ( message as DiagnosticMessageChain ) . messageText ;
194
+ }
195
+ function getDiagnosticOfFileFrom ( file : SourceFile | undefined , start : number | undefined , length : number | undefined , message : DiagnosticMessage | DiagnosticMessageChain , ..._args : ( string | number ) [ ] ) : Diagnostic {
196
+ let text : DiagnosticMessageChain | string ;
197
+ if ( isDiagnosticMessageChain ( message ) ) {
198
+ text = message ;
199
+ }
200
+ else {
201
+ text = getLocaleSpecificMessage ( message ) ;
202
+
203
+ if ( arguments . length > 4 ) {
204
+ text = formatStringFromArgs ( text , arguments , 4 ) ;
205
+ }
206
+ }
207
+
186
208
return {
187
209
file,
188
210
start,
@@ -194,40 +216,22 @@ namespace ts.tscWatch {
194
216
} ;
195
217
}
196
218
197
- function getDiagnosticWithoutFile ( message : DiagnosticMessage , ..._args : ( string | number ) [ ] ) : Diagnostic {
198
- let text = getLocaleSpecificMessage ( message ) ;
199
-
200
- if ( arguments . length > 1 ) {
201
- text = formatStringFromArgs ( text , arguments , 1 ) ;
202
- }
203
-
204
- return getDiagnosticOfFileFrom ( /*file*/ undefined , text , /*start*/ undefined , /*length*/ undefined , message ) ;
219
+ function getDiagnosticWithoutFile ( message : DiagnosticMessage | DiagnosticMessageChain , ...args : ( string | number ) [ ] ) : Diagnostic {
220
+ return getDiagnosticOfFileFrom ( /*file*/ undefined , /*start*/ undefined , /*length*/ undefined , message , ...args ) ;
205
221
}
206
222
207
- function getDiagnosticOfFile ( file : SourceFile , start : number , length : number , message : DiagnosticMessage , ..._args : ( string | number ) [ ] ) : Diagnostic {
208
- let text = getLocaleSpecificMessage ( message ) ;
209
-
210
- if ( arguments . length > 4 ) {
211
- text = formatStringFromArgs ( text , arguments , 4 ) ;
212
- }
213
-
214
- return getDiagnosticOfFileFrom ( file , text , start , length , message ) ;
223
+ function getDiagnosticOfFile ( file : SourceFile , start : number , length : number , message : DiagnosticMessage | DiagnosticMessageChain , ...args : ( string | number ) [ ] ) : Diagnostic {
224
+ return getDiagnosticOfFileFrom ( file , start , length , message , ...args ) ;
215
225
}
216
226
217
227
function getUnknownCompilerOption ( program : Program , configFile : File , option : string ) {
218
228
const quotedOption = `"${ option } "` ;
219
229
return getDiagnosticOfFile ( program . getCompilerOptions ( ) . configFile ! , configFile . content . indexOf ( quotedOption ) , quotedOption . length , Diagnostics . Unknown_compiler_option_0 , option ) ;
220
230
}
221
231
222
- function getDiagnosticOfFileFromProgram ( program : Program , filePath : string , start : number , length : number , message : DiagnosticMessage , ..._args : ( string | number ) [ ] ) : Diagnostic {
223
- let text = getLocaleSpecificMessage ( message ) ;
224
-
225
- if ( arguments . length > 5 ) {
226
- text = formatStringFromArgs ( text , arguments , 5 ) ;
227
- }
228
-
232
+ function getDiagnosticOfFileFromProgram ( program : Program , filePath : string , start : number , length : number , message : DiagnosticMessage | DiagnosticMessageChain , ...args : ( string | number ) [ ] ) : Diagnostic {
229
233
return getDiagnosticOfFileFrom ( program . getSourceFileByPath ( toPath ( filePath , program . getCurrentDirectory ( ) , s => s . toLowerCase ( ) ) ) ! ,
230
- text , start , length , message ) ;
234
+ start , length , message , ... args ) ;
231
235
}
232
236
233
237
function getDiagnosticModuleNotFoundOfFile ( program : Program , file : File , moduleName : string ) {
@@ -1706,6 +1710,125 @@ interface Document {
1706
1710
} ) ;
1707
1711
} ) ;
1708
1712
1713
+ describe ( "Emit times and Error updates in builder after program changes" , ( ) => {
1714
+ function getOutputFileStampAndError ( host : WatchedSystem , watch : Watch , file : File ) {
1715
+ const builderProgram = watch . getBuilderProgram ( ) ;
1716
+ const state = builderProgram . getState ( ) ;
1717
+ return {
1718
+ file,
1719
+ fileStamp : host . getModifiedTime ( file . path . replace ( ".ts" , ".js" ) ) ,
1720
+ errors : builderProgram . getSemanticDiagnostics ( watch ( ) . getSourceFileByPath ( file . path as Path ) ) ,
1721
+ errorsFromOldState : ! ! state . semanticDiagnosticsFromOldState && state . semanticDiagnosticsFromOldState . has ( file . path )
1722
+ } ;
1723
+ }
1724
+
1725
+ function getOutputFileStampsAndErrors ( host : WatchedSystem , watch : Watch , directoryFiles : ReadonlyArray < File > ) {
1726
+ return directoryFiles . map ( d => getOutputFileStampAndError ( host , watch , d ) ) ;
1727
+ }
1728
+
1729
+ function findStampAndErrors ( stampsAndErrors : ReadonlyArray < ReturnType < typeof getOutputFileStampAndError > > , file : File ) {
1730
+ return find ( stampsAndErrors , info => info . file === file ) ! ;
1731
+ }
1732
+
1733
+ function verifyOutputFileStampsAndErrors (
1734
+ file : File ,
1735
+ emitExpected : boolean ,
1736
+ errorRefershExpected : boolean ,
1737
+ beforeChangeFileStampsAndErrors : ReadonlyArray < ReturnType < typeof getOutputFileStampAndError > > ,
1738
+ afterChangeFileStampsAndErrors : ReadonlyArray < ReturnType < typeof getOutputFileStampAndError > >
1739
+ ) {
1740
+ const beforeChange = findStampAndErrors ( beforeChangeFileStampsAndErrors , file ) ;
1741
+ const afterChange = findStampAndErrors ( afterChangeFileStampsAndErrors , file ) ;
1742
+ if ( emitExpected ) {
1743
+ assert . notStrictEqual ( afterChange . fileStamp , beforeChange . fileStamp , `Expected emit for file ${ file . path } ` ) ;
1744
+ }
1745
+ else {
1746
+ assert . strictEqual ( afterChange . fileStamp , beforeChange . fileStamp , `Did not expect new emit for file ${ file . path } ` ) ;
1747
+ }
1748
+ if ( errorRefershExpected ) {
1749
+ if ( afterChange . errors !== emptyArray || beforeChange . errors !== emptyArray ) {
1750
+ assert . notStrictEqual ( afterChange . errors , beforeChange . errors , `Expected new errors for file ${ file . path } ` ) ;
1751
+ }
1752
+ assert . isFalse ( afterChange . errorsFromOldState , `Expected errors to be not copied from old state for file ${ file . path } ` ) ;
1753
+ }
1754
+ else {
1755
+ assert . strictEqual ( afterChange . errors , beforeChange . errors , `Expected errors to not change for file ${ file . path } ` ) ;
1756
+ assert . isTrue ( afterChange . errorsFromOldState , `Expected errors to be copied from old state for file ${ file . path } ` ) ;
1757
+ }
1758
+ }
1759
+
1760
+ it ( "updates errors in file not exporting a deep multilevel import that changes" , ( ) => {
1761
+ const currentDirectory = "/user/username/projects/myproject" ;
1762
+ const aFile : File = {
1763
+ path : `${ currentDirectory } /a.ts` ,
1764
+ content : `export interface Point {
1765
+ name: string;
1766
+ c: Coords;
1767
+ }
1768
+ export interface Coords {
1769
+ x2: number;
1770
+ y: number;
1771
+ }`
1772
+ } ;
1773
+ const bFile : File = {
1774
+ path : `${ currentDirectory } /b.ts` ,
1775
+ content : `import { Point } from "./a";
1776
+ export interface PointWrapper extends Point {
1777
+ }`
1778
+ } ;
1779
+ const cFile : File = {
1780
+ path : `${ currentDirectory } /c.ts` ,
1781
+ content : `import { PointWrapper } from "./b";
1782
+ export function getPoint(): PointWrapper {
1783
+ return {
1784
+ name: "test",
1785
+ c: {
1786
+ x: 1,
1787
+ y: 2
1788
+ }
1789
+ }
1790
+ };`
1791
+ } ;
1792
+ const dFile : File = {
1793
+ path : `${ currentDirectory } /d.ts` ,
1794
+ content : `import { getPoint } from "./c";
1795
+ getPoint().c.x;`
1796
+ } ;
1797
+ const eFile : File = {
1798
+ path : `${ currentDirectory } /e.ts` ,
1799
+ content : `import "./d";`
1800
+ } ;
1801
+ const config : File = {
1802
+ path : `${ currentDirectory } /tsconfig.json` ,
1803
+ content : `{}`
1804
+ } ;
1805
+ const directoryFiles = [ aFile , bFile , cFile , dFile , eFile ] ;
1806
+ const files = [ ...directoryFiles , config , libFile ] ;
1807
+ const host = createWatchedSystem ( files , { currentDirectory } ) ;
1808
+ const watch = createWatchOfConfigFile ( "tsconfig.json" , host ) ;
1809
+ checkProgramActualFiles ( watch ( ) , [ aFile . path , bFile . path , cFile . path , dFile . path , eFile . path , libFile . path ] ) ;
1810
+ checkOutputErrorsInitial ( host , [
1811
+ getDiagnosticOfFileFromProgram ( watch ( ) , cFile . path , cFile . content . indexOf ( "x: 1" ) , 4 , chainDiagnosticMessages (
1812
+ chainDiagnosticMessages ( /*details*/ undefined , Diagnostics . Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1 , "x" , "Coords" ) ,
1813
+ Diagnostics . Type_0_is_not_assignable_to_type_1 ,
1814
+ "{ x: number; y: number; }" ,
1815
+ "Coords"
1816
+ ) ) ,
1817
+ getDiagnosticOfFileFromProgram ( watch ( ) , dFile . path , dFile . content . lastIndexOf ( "x" ) , 1 , Diagnostics . Property_0_does_not_exist_on_type_1 , "x" , "Coords" )
1818
+ ] ) ;
1819
+ const beforeChange = getOutputFileStampsAndErrors ( host , watch , directoryFiles ) ;
1820
+ host . writeFile ( aFile . path , aFile . content . replace ( "x2" , "x" ) ) ;
1821
+ host . runQueuedTimeoutCallbacks ( ) ;
1822
+ checkOutputErrorsIncremental ( host , emptyArray ) ;
1823
+ const afterChange = getOutputFileStampsAndErrors ( host , watch , directoryFiles ) ;
1824
+ verifyOutputFileStampsAndErrors ( aFile , /*emitExpected*/ true , /*errorRefershExpected*/ true , beforeChange , afterChange ) ;
1825
+ verifyOutputFileStampsAndErrors ( bFile , /*emitExpected*/ true , /*errorRefershExpected*/ true , beforeChange , afterChange ) ;
1826
+ verifyOutputFileStampsAndErrors ( cFile , /*emitExpected*/ false , /*errorRefershExpected*/ true , beforeChange , afterChange ) ;
1827
+ verifyOutputFileStampsAndErrors ( dFile , /*emitExpected*/ false , /*errorRefershExpected*/ true , beforeChange , afterChange ) ;
1828
+ verifyOutputFileStampsAndErrors ( eFile , /*emitExpected*/ false , /*errorRefershExpected*/ false , beforeChange , afterChange ) ;
1829
+ } ) ;
1830
+ } ) ;
1831
+
1709
1832
describe ( "tsc-watch emit with outFile or out setting" , ( ) => {
1710
1833
function createWatchForOut ( out ?: string , outFile ?: string ) {
1711
1834
const host = createWatchedSystem ( [ ] ) ;
0 commit comments