@@ -14,30 +14,12 @@ const resolve = require('resolve');
1414const path = require ( 'path' ) ;
1515const paths = require ( '../../config/paths' ) ;
1616const os = require ( 'os' ) ;
17+ const immer = require ( 'react-dev-utils/immer' ) . produce ;
1718
1819function writeJson ( fileName , object ) {
1920 fs . writeFileSync ( fileName , JSON . stringify ( object , null , 2 ) + os . EOL ) ;
2021}
2122
22- const compilerOptions = {
23- // These are suggested values and will be set when not present in the
24- // tsconfig.json
25- target : { suggested : 'es5' } ,
26- allowJs : { suggested : true } ,
27- skipLibCheck : { suggested : true } ,
28- esModuleInterop : { suggested : true } ,
29- allowSyntheticDefaultImports : { suggested : true } ,
30- strict : { suggested : true } ,
31-
32- // These values are required and cannot be changed by the user
33- module : { value : 'esnext' , reason : 'for import() and import/export' } ,
34- moduleResolution : { value : 'node' , reason : 'to match webpack resolution' } ,
35- resolveJsonModule : { value : true , reason : 'to match webpack loader' } ,
36- isolatedModules : { value : true , reason : 'implementation limitation' } ,
37- noEmit : { value : true } ,
38- jsx : { value : 'preserve' , reason : 'JSX is compiled by Babel' } ,
39- } ;
40-
4123function verifyTypeScriptSetup ( ) {
4224 let firstTimeSetup = false ;
4325
@@ -86,48 +68,115 @@ function verifyTypeScriptSetup() {
8668 process . exit ( 1 ) ;
8769 }
8870
71+ const compilerOptions = {
72+ // These are suggested values and will be set when not present in the
73+ // tsconfig.json
74+ // 'parsedValue' matches the output value from ts.parseJsonConfigFileContent()
75+ target : {
76+ parsedValue : ts . ScriptTarget . ES5 ,
77+ suggested : 'es5' ,
78+ } ,
79+ allowJs : { suggested : true } ,
80+ skipLibCheck : { suggested : true } ,
81+ esModuleInterop : { suggested : true } ,
82+ allowSyntheticDefaultImports : { suggested : true } ,
83+ strict : { suggested : true } ,
84+
85+ // These values are required and cannot be changed by the user
86+ module : {
87+ parsedValue : ts . ModuleKind . ESNext ,
88+ value : 'esnext' ,
89+ reason : 'for import() and import/export' ,
90+ } ,
91+ moduleResolution : {
92+ parsedValue : ts . ModuleResolutionKind . NodeJs ,
93+ value : 'node' ,
94+ reason : 'to match webpack resolution' ,
95+ } ,
96+ resolveJsonModule : { value : true , reason : 'to match webpack loader' } ,
97+ isolatedModules : { value : true , reason : 'implementation limitation' } ,
98+ noEmit : { value : true } ,
99+ jsx : {
100+ parsedValue : ts . JsxEmit . Preserve ,
101+ value : 'preserve' ,
102+ reason : 'JSX is compiled by Babel' ,
103+ } ,
104+ } ;
105+
106+ const formatDiagnosticHost = {
107+ getCanonicalFileName : fileName => fileName ,
108+ getCurrentDirectory : ts . sys . getCurrentDirectory ,
109+ getNewLine : ( ) => os . EOL ,
110+ } ;
111+
89112 const messages = [ ] ;
90- let tsconfig ;
113+ let appTsConfig ;
114+ let parsedTsConfig ;
115+ let parsedCompilerOptions ;
91116 try {
92- const { config, error } = ts . readConfigFile (
117+ const { config : readTsConfig , error } = ts . readConfigFile (
93118 paths . appTsConfig ,
94119 ts . sys . readFile
95120 ) ;
96121
97122 if ( error ) {
98- throw error ;
123+ throw new Error ( ts . formatDiagnostic ( error , formatDiagnosticHost ) ) ;
99124 }
100125
101- tsconfig = config ;
102- } catch ( _ ) {
126+ appTsConfig = readTsConfig ;
127+
128+ // Get TS to parse and resolve any "extends"
129+ // Calling this function also mutates the tsconfig above,
130+ // adding in "include" and "exclude", but the compilerOptions remain untouched
131+ let result ;
132+ parsedTsConfig = immer ( readTsConfig , config => {
133+ result = ts . parseJsonConfigFileContent (
134+ config ,
135+ ts . sys ,
136+ path . dirname ( paths . appTsConfig )
137+ ) ;
138+ } ) ;
139+
140+ if ( result . errors && result . errors . length ) {
141+ throw new Error (
142+ ts . formatDiagnostic ( result . errors [ 0 ] , formatDiagnosticHost )
143+ ) ;
144+ }
145+
146+ parsedCompilerOptions = result . options ;
147+ } catch ( e ) {
103148 console . error (
104149 chalk . red . bold (
105150 'Could not parse' ,
106151 chalk . cyan ( 'tsconfig.json' ) + '.' ,
107152 'Please make sure it contains syntactically correct JSON.'
108153 )
109154 ) ;
155+ console . error ( e && e . message ? `Details: ${ e . message } ` : '' ) ;
110156 process . exit ( 1 ) ;
111157 }
112158
113- if ( tsconfig . compilerOptions == null ) {
114- tsconfig . compilerOptions = { } ;
159+ if ( appTsConfig . compilerOptions == null ) {
160+ appTsConfig . compilerOptions = { } ;
115161 firstTimeSetup = true ;
116162 }
117163
118164 for ( const option of Object . keys ( compilerOptions ) ) {
119- const { value, suggested, reason } = compilerOptions [ option ] ;
165+ const { parsedValue, value, suggested, reason } = compilerOptions [ option ] ;
166+
167+ const valueToCheck = parsedValue === undefined ? value : parsedValue ;
168+
120169 if ( suggested != null ) {
121- if ( tsconfig . compilerOptions [ option ] === undefined ) {
122- tsconfig . compilerOptions [ option ] = suggested ;
170+ if ( parsedCompilerOptions [ option ] === undefined ) {
171+ appTsConfig . compilerOptions [ option ] = suggested ;
123172 messages . push (
124173 `${ chalk . cyan ( 'compilerOptions.' + option ) } to be ${ chalk . bold (
125174 'suggested'
126175 ) } value: ${ chalk . cyan . bold ( suggested ) } (this can be changed)`
127176 ) ;
128177 }
129- } else if ( tsconfig . compilerOptions [ option ] !== value ) {
130- tsconfig . compilerOptions [ option ] = value ;
178+ } else if ( parsedCompilerOptions [ option ] !== valueToCheck ) {
179+ appTsConfig . compilerOptions [ option ] = value ;
131180 messages . push (
132181 `${ chalk . cyan ( 'compilerOptions.' + option ) } ${ chalk . bold (
133182 'must'
@@ -137,14 +186,15 @@ function verifyTypeScriptSetup() {
137186 }
138187 }
139188
140- if ( tsconfig . include == null ) {
141- tsconfig . include = [ 'src' ] ;
189+ // tsconfig will have the merged "include" and "exclude" by this point
190+ if ( parsedTsConfig . include == null ) {
191+ appTsConfig . include = [ 'src' ] ;
142192 messages . push (
143193 `${ chalk . cyan ( 'include' ) } should be ${ chalk . cyan . bold ( 'src' ) } `
144194 ) ;
145195 }
146- if ( tsconfig . exclude == null ) {
147- tsconfig . exclude = [ '**/__tests__/**' , '**/?*test.*' , '**/?*spec.*' ] ;
196+ if ( parsedTsConfig . exclude == null ) {
197+ appTsConfig . exclude = [ '**/__tests__/**' , '**/?*test.*' , '**/?*spec.*' ] ;
148198 messages . push ( `${ chalk . cyan ( 'exclude' ) } should exclude test files` ) ;
149199 }
150200
@@ -171,7 +221,7 @@ function verifyTypeScriptSetup() {
171221 } ) ;
172222 console . warn ( ) ;
173223 }
174- writeJson ( paths . appTsConfig , tsconfig ) ;
224+ writeJson ( paths . appTsConfig , appTsConfig ) ;
175225 }
176226
177227 // Copy type declarations associated with this version of `react-scripts`
0 commit comments