2
2
const os = require ( 'os' ) ;
3
3
const path = require ( 'path' ) ;
4
4
const { outputJson, outputJsonSync} = require ( 'fs-extra' ) ;
5
+ const pkg = require ( '../package.json' ) ;
5
6
const arrify = require ( 'arrify' ) ;
6
7
const mergeWith = require ( 'lodash/mergeWith' ) ;
8
+ const groupBy = require ( 'lodash/groupBy' ) ;
7
9
const flow = require ( 'lodash/flow' ) ;
8
10
const pathExists = require ( 'path-exists' ) ;
9
11
const findCacheDir = require ( 'find-cache-dir' ) ;
@@ -15,7 +17,8 @@ const pReduce = require('p-reduce');
15
17
const micromatch = require ( 'micromatch' ) ;
16
18
const JSON5 = require ( 'json5' ) ;
17
19
const toAbsoluteGlob = require ( 'to-absolute-glob' ) ;
18
- const tempy = require ( 'tempy' ) ;
20
+ const stringify = require ( 'json-stable-stringify-without-jsonify' ) ;
21
+ const murmur = require ( 'imurmurhash' ) ;
19
22
const {
20
23
DEFAULT_IGNORES ,
21
24
DEFAULT_EXTENSION ,
@@ -28,10 +31,13 @@ const {
28
31
TSCONFIG_DEFFAULTS
29
32
} = require ( './constants' ) ;
30
33
34
+ const nodeVersion = process && process . version ;
35
+ const cacheLocation = findCacheDir ( { name : 'xo' } ) || path . join ( os . homedir ( ) || os . tmpdir ( ) , '.xo-cache/' ) ;
36
+
31
37
const DEFAULT_CONFIG = {
32
38
useEslintrc : false ,
33
39
cache : true ,
34
- cacheLocation : findCacheDir ( { name : 'xo' } ) || path . join ( os . homedir ( ) || os . tmpdir ( ) , '. xo-cache/ ' ) ,
40
+ cacheLocation : path . join ( cacheLocation , 'xo-cache.json ' ) ,
35
41
globInputPaths : false ,
36
42
baseConfig : {
37
43
extends : [
@@ -96,7 +102,7 @@ const mergeWithFileConfig = options => {
96
102
const tsConfigExplorer = cosmiconfigSync ( [ ] , { searchPlaces : [ 'tsconfig.json' ] , loaders : { '.json' : ( _ , content ) => JSON5 . parse ( content ) } } ) ;
97
103
const { config : tsConfig , filepath : tsConfigPath } = tsConfigExplorer . search ( options . filename ) || { } ;
98
104
99
- options . tsConfigPath = tempy . file ( { name : 'tsconfig.json' } ) ;
105
+ options . tsConfigPath = getTsConfigCachePath ( [ options . filename ] , options . tsConfigPath ) ;
100
106
options . ts = true ;
101
107
outputJsonSync ( options . tsConfigPath , makeTSConfig ( tsConfig , tsConfigPath , [ options . filename ] ) ) ;
102
108
}
@@ -111,7 +117,9 @@ The config files are searched starting from each files.
111
117
const mergeWithFileConfigs = async ( files , options ) => {
112
118
options . cwd = path . resolve ( options . cwd || process . cwd ( ) ) ;
113
119
114
- return Promise . all ( [ ...( await pReduce ( files . map ( file => path . resolve ( options . cwd , file ) ) , async ( configs , file ) => {
120
+ const tsConfigs = { } ;
121
+
122
+ const groups = [ ...( await pReduce ( files . map ( file => path . resolve ( options . cwd , file ) ) , async ( configs , file ) => {
115
123
const configExplorer = cosmiconfig ( MODULE_NAME , { searchPlaces : CONFIG_FILES , loaders : { noExt : defaultLoaders [ '.json' ] } , stopDir : options . cwd } ) ;
116
124
const pkgConfigExplorer = cosmiconfig ( 'engines' , { searchPlaces : [ 'package.json' ] , stopDir : options . cwd } ) ;
117
125
@@ -127,8 +135,9 @@ const mergeWithFileConfigs = async (files, options) => {
127
135
}
128
136
129
137
const { hash, options : optionsWithOverrides } = applyOverrides ( file , fileOptions ) ;
138
+ fileOptions = optionsWithOverrides ;
130
139
131
- const prettierConfigPath = optionsWithOverrides . prettier ? await prettier . resolveConfigFile ( file ) : undefined ;
140
+ const prettierConfigPath = fileOptions . prettier ? await prettier . resolveConfigFile ( file ) : undefined ;
132
141
const prettierOptions = prettierConfigPath ? await prettier . resolveConfig ( file , { config : prettierConfigPath } ) : { } ;
133
142
134
143
let tsConfigPath ;
@@ -138,38 +147,50 @@ const mergeWithFileConfigs = async (files, options) => {
138
147
const tsConfigExplorer = cosmiconfig ( [ ] , { searchPlaces : [ 'tsconfig.json' ] , loaders : { '.json' : ( _ , content ) => JSON5 . parse ( content ) } } ) ;
139
148
( { config : tsConfig , filepath : tsConfigPath } = await tsConfigExplorer . search ( file ) || { } ) ;
140
149
141
- optionsWithOverrides . tsConfigPath = tsConfigPath ;
142
- optionsWithOverrides . tsConfig = tsConfig ;
143
- optionsWithOverrides . ts = true ;
150
+ fileOptions . tsConfigPath = tsConfigPath ;
151
+ tsConfigs [ tsConfigPath || '' ] = tsConfig ;
152
+ fileOptions . ts = true ;
144
153
}
145
154
146
- const cacheKey = JSON . stringify ( { xoConfigPath, enginesConfigPath, prettierConfigPath, hash, tsConfigPath : fileOptions . tsConfigPath , ts : fileOptions . ts } ) ;
155
+ const cacheKey = stringify ( { xoConfigPath, enginesConfigPath, prettierConfigPath, hash, tsConfigPath : fileOptions . tsConfigPath , ts : fileOptions . ts } ) ;
147
156
const cachedGroup = configs . get ( cacheKey ) ;
148
157
149
158
configs . set ( cacheKey , {
150
159
files : [ file , ...( cachedGroup ? cachedGroup . files : [ ] ) ] ,
151
- options : cachedGroup ? cachedGroup . options : optionsWithOverrides ,
160
+ options : cachedGroup ? cachedGroup . options : fileOptions ,
152
161
prettierOptions
153
162
} ) ;
154
163
155
164
return configs ;
156
- } , new Map ( ) ) ) . values ( ) ] . map ( async group => {
157
- const { files, options} = group ;
158
- if ( options . ts ) {
159
- const tsConfigPath = tempy . file ( { name : 'tsconfig.json' } ) ;
160
- await outputJson ( tsConfigPath , makeTSConfig ( options . tsConfig , options . tsConfigPath , files ) ) ;
161
- group . options . tsConfigPath = tsConfigPath ;
162
- delete group . options . tsConfig ;
165
+ } , new Map ( ) ) ) . values ( ) ] ;
166
+
167
+ await Promise . all ( Object . entries ( groupBy ( groups . filter ( ( { options} ) => Boolean ( options . ts ) ) , group => group . options . tsConfigPath || '' ) ) . map (
168
+ ( [ tsConfigPath , groups ] ) => {
169
+ const files = [ ] . concat ( ...groups . map ( group => group . files ) ) ;
170
+ const cachePath = getTsConfigCachePath ( files , tsConfigPath ) ;
171
+ groups . forEach ( group => {
172
+ group . options . tsConfigPath = cachePath ;
173
+ } ) ;
174
+ return outputJson ( cachePath , makeTSConfig ( tsConfigs [ tsConfigPath ] , tsConfigPath , files ) ) ;
163
175
}
176
+ ) ) ;
164
177
165
- return group ;
166
- } ) ) ;
178
+ return groups ;
167
179
} ;
168
180
181
+ /**
182
+ Generate a unique and consistent path for the temporary `tsconfig.json`.
183
+ Hashing based on https://github.com/eslint/eslint/blob/cf38d0d939b62f3670cdd59f0143fd896fccd771/lib/cli-engine/lint-result-cache.js#L30
184
+ */
185
+ const getTsConfigCachePath = ( files , tsConfigPath ) => path . join (
186
+ cacheLocation ,
187
+ `tsconfig.${ murmur ( `${ pkg . version } _${ nodeVersion } _${ stringify ( { files, tsConfigPath : tsConfigPath } ) } ` ) . result ( ) . toString ( 36 ) } .json`
188
+ ) ;
189
+
169
190
const makeTSConfig = ( tsConfig , tsConfigPath , files ) => {
170
191
const config = { files : files . filter ( isTypescript ) } ;
171
192
172
- if ( tsConfigPath ) {
193
+ if ( tsConfig ) {
173
194
config . extends = tsConfigPath ;
174
195
config . include = arrify ( tsConfig . include ) . map ( pattern => toAbsoluteGlob ( pattern , { cwd : path . dirname ( tsConfigPath ) } ) ) ;
175
196
} else {
0 commit comments