@@ -14,30 +14,12 @@ const resolve = require('resolve');
14
14
const path = require ( 'path' ) ;
15
15
const paths = require ( '../../config/paths' ) ;
16
16
const os = require ( 'os' ) ;
17
+ const immer = require ( 'react-dev-utils/immer' ) . produce ;
17
18
18
19
function writeJson ( fileName , object ) {
19
20
fs . writeFileSync ( fileName , JSON . stringify ( object , null , 2 ) + os . EOL ) ;
20
21
}
21
22
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
-
41
23
function verifyTypeScriptSetup ( ) {
42
24
let firstTimeSetup = false ;
43
25
@@ -86,48 +68,115 @@ function verifyTypeScriptSetup() {
86
68
process . exit ( 1 ) ;
87
69
}
88
70
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
+
89
112
const messages = [ ] ;
90
- let tsconfig ;
113
+ let appTsConfig ;
114
+ let parsedTsConfig ;
115
+ let parsedCompilerOptions ;
91
116
try {
92
- const { config, error } = ts . readConfigFile (
117
+ const { config : readTsConfig , error } = ts . readConfigFile (
93
118
paths . appTsConfig ,
94
119
ts . sys . readFile
95
120
) ;
96
121
97
122
if ( error ) {
98
- throw error ;
123
+ throw new Error ( ts . formatDiagnostic ( error , formatDiagnosticHost ) ) ;
99
124
}
100
125
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 ) {
103
148
console . error (
104
149
chalk . red . bold (
105
150
'Could not parse' ,
106
151
chalk . cyan ( 'tsconfig.json' ) + '.' ,
107
152
'Please make sure it contains syntactically correct JSON.'
108
153
)
109
154
) ;
155
+ console . error ( e && e . message ? `Details: ${ e . message } ` : '' ) ;
110
156
process . exit ( 1 ) ;
111
157
}
112
158
113
- if ( tsconfig . compilerOptions == null ) {
114
- tsconfig . compilerOptions = { } ;
159
+ if ( appTsConfig . compilerOptions == null ) {
160
+ appTsConfig . compilerOptions = { } ;
115
161
firstTimeSetup = true ;
116
162
}
117
163
118
164
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
+
120
169
if ( suggested != null ) {
121
- if ( tsconfig . compilerOptions [ option ] === undefined ) {
122
- tsconfig . compilerOptions [ option ] = suggested ;
170
+ if ( parsedCompilerOptions [ option ] === undefined ) {
171
+ appTsConfig . compilerOptions [ option ] = suggested ;
123
172
messages . push (
124
173
`${ chalk . cyan ( 'compilerOptions.' + option ) } to be ${ chalk . bold (
125
174
'suggested'
126
175
) } value: ${ chalk . cyan . bold ( suggested ) } (this can be changed)`
127
176
) ;
128
177
}
129
- } else if ( tsconfig . compilerOptions [ option ] !== value ) {
130
- tsconfig . compilerOptions [ option ] = value ;
178
+ } else if ( parsedCompilerOptions [ option ] !== valueToCheck ) {
179
+ appTsConfig . compilerOptions [ option ] = value ;
131
180
messages . push (
132
181
`${ chalk . cyan ( 'compilerOptions.' + option ) } ${ chalk . bold (
133
182
'must'
@@ -137,14 +186,15 @@ function verifyTypeScriptSetup() {
137
186
}
138
187
}
139
188
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' ] ;
142
192
messages . push (
143
193
`${ chalk . cyan ( 'include' ) } should be ${ chalk . cyan . bold ( 'src' ) } `
144
194
) ;
145
195
}
146
- if ( tsconfig . exclude == null ) {
147
- tsconfig . exclude = [ '**/__tests__/**' , '**/?*test.*' , '**/?*spec.*' ] ;
196
+ if ( parsedTsConfig . exclude == null ) {
197
+ appTsConfig . exclude = [ '**/__tests__/**' , '**/?*test.*' , '**/?*spec.*' ] ;
148
198
messages . push ( `${ chalk . cyan ( 'exclude' ) } should exclude test files` ) ;
149
199
}
150
200
@@ -171,7 +221,7 @@ function verifyTypeScriptSetup() {
171
221
} ) ;
172
222
console . warn ( ) ;
173
223
}
174
- writeJson ( paths . appTsConfig , tsconfig ) ;
224
+ writeJson ( paths . appTsConfig , appTsConfig ) ;
175
225
}
176
226
177
227
// Copy type declarations associated with this version of `react-scripts`
0 commit comments