Skip to content

Commit b0dfcbd

Browse files
pvdlgsindresorhus
andauthoredFeb 24, 2020
Add TypeScript support (#426)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
1 parent 4cefdbc commit b0dfcbd

13 files changed

+322
-24
lines changed
 

Diff for: ‎config/default.tsconfig.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"include": [
3+
"**/*d.ts"
4+
]
5+
}

Diff for: ‎lib/constants.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const DEFAULT_IGNORES = [
1616
*/
1717
const MERGE_OPTIONS_CONCAT = ['extends', 'envs', 'globals', 'plugins'];
1818

19-
const DEFAULT_EXTENSION = ['js', 'jsx'];
19+
const TYPESCRIPT_EXTENSION = ['ts', 'tsx'];
20+
const DEFAULT_EXTENSION = ['js', 'jsx', ...TYPESCRIPT_EXTENSION];
2021

2122
/**
2223
* Define the rules config that are overwritten only for specific version of Node.js based on `engines.node` in package.json or the `node-version` option.
@@ -90,7 +91,6 @@ const ENGINE_RULES = {
9091
};
9192

9293
const PRETTIER_CONFIG_OVERRIDE = {
93-
'@typescript-eslint/eslint-plugin': 'prettier/@typescript-eslint',
9494
'eslint-plugin-babel': 'prettier/babel',
9595
'eslint-plugin-flowtype': 'prettier/flowtype',
9696
'eslint-plugin-react': 'prettier/react',
@@ -108,12 +108,26 @@ const CONFIG_FILES = [
108108
`${MODULE_NAME}.config.js`
109109
];
110110

111+
const TSCONFIG_DEFFAULTS = {
112+
compilerOptions: {
113+
target: 'es2018',
114+
newLine: 'lf',
115+
strict: true,
116+
noImplicitReturns: true,
117+
noUnusedLocals: true,
118+
noUnusedParameters: true,
119+
noFallthroughCasesInSwitch: true
120+
}
121+
};
122+
111123
module.exports = {
112124
DEFAULT_IGNORES,
113125
DEFAULT_EXTENSION,
126+
TYPESCRIPT_EXTENSION,
114127
ENGINE_RULES,
115128
PRETTIER_CONFIG_OVERRIDE,
116129
MODULE_NAME,
117130
CONFIG_FILES,
118-
MERGE_OPTIONS_CONCAT
131+
MERGE_OPTIONS_CONCAT,
132+
TSCONFIG_DEFFAULTS
119133
};

Diff for: ‎lib/options-manager.js

+94-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
const os = require('os');
33
const path = require('path');
4+
const {outputJson, outputJsonSync} = require('fs-extra');
45
const arrify = require('arrify');
56
const mergeWith = require('lodash/mergeWith');
67
const flow = require('lodash/flow');
@@ -12,13 +13,19 @@ const semver = require('semver');
1213
const {cosmiconfig, cosmiconfigSync, defaultLoaders} = require('cosmiconfig');
1314
const pReduce = require('p-reduce');
1415
const micromatch = require('micromatch');
16+
const JSON5 = require('json5');
17+
const toAbsoluteGlob = require('to-absolute-glob');
18+
const tempy = require('tempy');
1519
const {
1620
DEFAULT_IGNORES,
1721
DEFAULT_EXTENSION,
22+
TYPESCRIPT_EXTENSION,
1823
ENGINE_RULES,
1924
PRETTIER_CONFIG_OVERRIDE,
20-
MODULE_NAME, CONFIG_FILES,
21-
MERGE_OPTIONS_CONCAT
25+
MODULE_NAME,
26+
CONFIG_FILES,
27+
MERGE_OPTIONS_CONCAT,
28+
TSCONFIG_DEFFAULTS
2229
} = require('./constants');
2330

2431
const DEFAULT_CONFIG = {
@@ -57,6 +64,8 @@ const mergeFn = (previousValue, value, key) => {
5764
}
5865
};
5966

67+
const isTypescript = file => TYPESCRIPT_EXTENSION.includes(path.extname(file).slice(1));
68+
6069
/**
6170
* Find config for `lintText`.
6271
* The config files are searched starting from `options.filename` if defined or `options.cwd` otherwise.
@@ -83,6 +92,15 @@ const mergeWithFileConfig = options => {
8392

8493
const prettierOptions = options.prettier ? prettier.resolveConfig.sync(searchPath) || {} : {};
8594

95+
if (options.filename && isTypescript(options.filename)) {
96+
const tsConfigExplorer = cosmiconfigSync([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}});
97+
const {config: tsConfig, filepath: tsConfigPath} = tsConfigExplorer.search(options.filename) || {};
98+
99+
options.tsConfigPath = tempy.file({name: 'tsconfig.json'});
100+
options.ts = true;
101+
outputJsonSync(options.tsConfigPath, makeTSConfig(tsConfig, tsConfigPath, [options.filename]));
102+
}
103+
86104
return {options, prettierOptions};
87105
};
88106

@@ -92,38 +110,73 @@ const mergeWithFileConfig = options => {
92110
*/
93111
const mergeWithFileConfigs = async (files, options) => {
94112
options.cwd = path.resolve(options.cwd || process.cwd());
95-
return [...(await pReduce(files, async (configs, file) => {
113+
114+
return Promise.all([...(await pReduce(files.map(file => path.resolve(options.cwd, file)), async (configs, file) => {
96115
const configExplorer = cosmiconfig(MODULE_NAME, {searchPlaces: CONFIG_FILES, loaders: {noExt: defaultLoaders['.json']}, stopDir: options.cwd});
97116
const pkgConfigExplorer = cosmiconfig('engines', {searchPlaces: ['package.json'], stopDir: options.cwd});
98-
const filepath = path.resolve(options.cwd, file);
99117

100-
const {config: xoOptions, filepath: xoConfigPath} = await configExplorer.search(filepath) || {};
101-
const {config: enginesOptions, filepath: enginesConfigPath} = await pkgConfigExplorer.search(filepath) || {};
118+
const {config: xoOptions, filepath: xoConfigPath} = await configExplorer.search(file) || {};
119+
const {config: enginesOptions, filepath: enginesConfigPath} = await pkgConfigExplorer.search(file) || {};
102120

103121
let fileOptions = mergeOptions(xoOptions, enginesOptions, options);
104122
fileOptions.cwd = xoConfigPath && path.dirname(xoConfigPath) !== fileOptions.cwd ? path.resolve(fileOptions.cwd, path.dirname(xoConfigPath)) : fileOptions.cwd;
105123

106-
if (!fileOptions.extensions.includes(path.extname(filepath).replace('.', '')) || isFileIgnored(filepath, fileOptions)) {
124+
if (!fileOptions.extensions.includes(path.extname(file).replace('.', '')) || isFileIgnored(file, fileOptions)) {
107125
// File extension/path is ignored, skip it
108126
return configs;
109127
}
110128

111-
const {hash, options: optionsWithOverrides} = applyOverrides(filepath, fileOptions);
129+
const {hash, options: optionsWithOverrides} = applyOverrides(file, fileOptions);
130+
131+
const prettierConfigPath = optionsWithOverrides.prettier ? await prettier.resolveConfigFile(file) : undefined;
132+
const prettierOptions = prettierConfigPath ? await prettier.resolveConfig(file, {config: prettierConfigPath}) : {};
112133

113-
const prettierConfigPath = optionsWithOverrides.prettier ? await prettier.resolveConfigFile(filepath) : undefined;
114-
const prettierOptions = prettierConfigPath ? await prettier.resolveConfig(filepath, {config: prettierConfigPath}) : {};
134+
let tsConfigPath;
135+
if (isTypescript(file)) {
136+
let tsConfig;
137+
// Override cosmiconfig `loaders` as we look only for the path of tsconfig.json, but not its content
138+
const tsConfigExplorer = cosmiconfig([], {searchPlaces: ['tsconfig.json'], loaders: {'.json': (_, content) => JSON5.parse(content)}});
139+
({config: tsConfig, filepath: tsConfigPath} = await tsConfigExplorer.search(file) || {});
115140

116-
const cacheKey = JSON.stringify({xoConfigPath, enginesConfigPath, prettierConfigPath, hash});
141+
optionsWithOverrides.tsConfigPath = tsConfigPath;
142+
optionsWithOverrides.tsConfig = tsConfig;
143+
optionsWithOverrides.ts = true;
144+
}
145+
146+
const cacheKey = JSON.stringify({xoConfigPath, enginesConfigPath, prettierConfigPath, hash, tsConfigPath: fileOptions.tsConfigPath, ts: fileOptions.ts});
117147
const cachedGroup = configs.get(cacheKey);
118148

119149
configs.set(cacheKey, {
120-
files: [filepath, ...(cachedGroup ? cachedGroup.files : [])],
150+
files: [file, ...(cachedGroup ? cachedGroup.files : [])],
121151
options: cachedGroup ? cachedGroup.options : optionsWithOverrides,
122152
prettierOptions
123153
});
124154

125155
return configs;
126-
}, new Map())).values()];
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;
163+
}
164+
165+
return group;
166+
}));
167+
};
168+
169+
const makeTSConfig = (tsConfig, tsConfigPath, files) => {
170+
const config = {files: files.filter(isTypescript)};
171+
172+
if (tsConfigPath) {
173+
config.extends = tsConfigPath;
174+
config.include = arrify(tsConfig.include).map(pattern => toAbsoluteGlob(pattern, {cwd: path.dirname(tsConfigPath)}));
175+
} else {
176+
Object.assign(config, TSCONFIG_DEFFAULTS);
177+
}
178+
179+
return config;
127180
};
128181

129182
const normalizeOptions = options => {
@@ -191,7 +244,8 @@ const buildConfig = (options, prettierOptions) =>
191244
flow(
192245
buildXOConfig(options),
193246
buildExtendsConfig(options),
194-
buildPrettierConfig(options, prettierOptions)
247+
buildPrettierConfig(options, prettierOptions),
248+
buildTSConfig(options)
195249
)(mergeWith(getEmptyOptions(), DEFAULT_CONFIG, normalizeOptions(options), mergeFn));
196250

197251
const buildXOConfig = options => config => {
@@ -212,7 +266,11 @@ const buildXOConfig = options => config => {
212266
}
213267

214268
if (options.space && !options.prettier) {
215-
config.rules.indent = ['error', spaces, {SwitchCase: 1}];
269+
if (options.ts) {
270+
config.rules['@typescript-eslint/indent'] = ['error', spaces, {SwitchCase: 1}];
271+
} else {
272+
config.rules.indent = ['error', spaces, {SwitchCase: 1}];
273+
}
216274

217275
// Only apply if the user has the React plugin
218276
if (options.cwd && resolveFrom.silent(options.cwd, 'eslint-plugin-react')) {
@@ -338,6 +396,27 @@ const mergeWithPrettierConfig = (options, prettierOptions) => {
338396
);
339397
};
340398

399+
const buildTSConfig = options => config => {
400+
if (options.ts) {
401+
config.baseConfig.extends = config.baseConfig.extends.concat('xo-typescript');
402+
config.baseConfig.parser = require.resolve('@typescript-eslint/parser');
403+
config.baseConfig.parserOptions = {
404+
warnOnUnsupportedTypeScriptVersion: false,
405+
ecmaFeatures: {jsx: true},
406+
project: options.tsConfigPath
407+
};
408+
409+
if (options.prettier) {
410+
config.baseConfig.extends = config.baseConfig.extends.concat('prettier/@typescript-eslint');
411+
}
412+
413+
delete config.tsConfigPath;
414+
delete config.ts;
415+
}
416+
417+
return config;
418+
};
419+
341420
const applyOverrides = (file, options) => {
342421
if (options.overrides && options.overrides.length > 0) {
343422
const {overrides} = options;

Diff for: ‎package.json

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "xo",
33
"version": "0.26.1",
4-
"description": "JavaScript linter with great defaults",
4+
"description": "JavaScript/TypeScript linter with great defaults",
55
"license": "MIT",
66
"repository": "xojs/xo",
77
"funding": "https://github.com/sponsors/sindresorhus",
@@ -46,15 +46,20 @@
4646
"verify",
4747
"enforce",
4848
"hint",
49-
"simple"
49+
"simple",
50+
"javascript",
51+
"typescript"
5052
],
5153
"dependencies": {
54+
"@typescript-eslint/eslint-plugin": "^2.19.2",
55+
"@typescript-eslint/parser": "^2.19.2",
5256
"arrify": "^2.0.1",
5357
"cosmiconfig": "^6.0.0",
5458
"debug": "^4.1.0",
5559
"eslint": "^6.8.0",
5660
"eslint-config-prettier": "^6.10.0",
5761
"eslint-config-xo": "^0.29.0",
62+
"eslint-config-xo-typescript": "^0.26.0",
5863
"eslint-formatter-pretty": "^3.0.1",
5964
"eslint-plugin-ava": "^10.0.1",
6065
"eslint-plugin-eslint-comments": "^3.1.2",
@@ -65,9 +70,11 @@
6570
"eslint-plugin-promise": "^4.2.1",
6671
"eslint-plugin-unicorn": "^16.1.1",
6772
"find-cache-dir": "^3.0.0",
73+
"fs-extra": "^8.1.0",
6874
"get-stdin": "^7.0.0",
6975
"globby": "^9.0.0",
7076
"has-flag": "^4.0.0",
77+
"json5": "^2.1.1",
7178
"lodash": "^4.17.15",
7279
"meow": "^5.0.0",
7380
"micromatch": "^4.0.2",
@@ -79,6 +86,8 @@
7986
"resolve-from": "^5.0.0",
8087
"semver": "^7.1.3",
8188
"slash": "^3.0.0",
89+
"tempy": "^0.4.0",
90+
"to-absolute-glob": "^2.0.2",
8291
"update-notifier": "^4.0.0"
8392
},
8493
"devDependencies": {
@@ -91,7 +100,8 @@
91100
"nyc": "^15.0.0",
92101
"pify": "^4.0.0",
93102
"proxyquire": "^2.1.3",
94-
"temp-write": "^4.0.0"
103+
"temp-write": "^4.0.0",
104+
"typescript": "^3.7.5"
95105
},
96106
"eslintConfig": {
97107
"extends": "eslint-config-xo"

Diff for: ‎readme.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<br>
77
</h1>
88

9-
> JavaScript linter with great defaults
9+
> JavaScript/TypeScript linter with great defaults
1010
1111
[![Build Status](https://travis-ci.org/xojs/xo.svg?branch=master)](https://travis-ci.org/xojs/xo) [![Coverage Status](https://coveralls.io/repos/github/xojs/xo/badge.svg?branch=master)](https://coveralls.io/github/xojs/xo?branch=master) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) [![Gitter](https://badges.gitter.im/join_chat.svg)](https://gitter.im/xojs/Lobby)
1212

@@ -25,8 +25,9 @@ Uses [ESLint](https://eslint.org) underneath, so issues regarding rules should b
2525
- Beautiful output.
2626
- Zero-config, but [configurable when needed](#config).
2727
- Enforces readable code, because you read more code than you write.
28-
- No need to specify file paths to lint as it lints all JS files except for [commonly ignored paths](#ignores).
28+
- No need to specify file paths to lint as it lints all JS/TS files except for [commonly ignored paths](#ignores).
2929
- [Config overrides per files/globs.](#config-overrides)
30+
- [TypeScript supported by default](#typescript)
3031
- Includes many useful ESLint plugins, like [`unicorn`](https://github.com/sindresorhus/eslint-plugin-unicorn), [`import`](https://github.com/benmosher/eslint-plugin-import), [`ava`](https://github.com/avajs/eslint-plugin-ava), [`node`](https://github.com/mysticatea/eslint-plugin-node) and more.
3132
- Automatically enables rules based on the [`engines`](https://docs.npmjs.com/files/package.json#engines) field in your `package.json`.
3233
- Caches results between runs for much better performance.
@@ -272,7 +273,9 @@ Enforce ES2015+ rules. Disabling this will make it not *enforce* ES2015+ syntax
272273

273274
### TypeScript
274275

275-
See [eslint-config-xo-typescript#use-with-xo](https://github.com/xojs/eslint-config-xo-typescript#use-with-xo)
276+
XO will automatically lint TypeScript files (`.ts`, `.d.ts` and `.tsx`) with the rules defined in [eslint-config-xo-typescript#use-with-xo](https://github.com/xojs/eslint-config-xo-typescript#use-with-xo).
277+
278+
XO will handle the [@typescript-eslint/parser `project` option](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser#parseroptionsproject) automatically even if you don't have a `tsconfig.json` in your project.
276279

277280
### Flow
278281

Diff for: ‎test/fixtures/typescript/child/extra-semicolon.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log('extra-semicolon');;

Diff for: ‎test/fixtures/typescript/child/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"xo": {
3+
"semicolon": false
4+
}
5+
}

Diff for: ‎test/fixtures/typescript/child/tsconfig.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"include": [
3+
"**/*.ts",
4+
"**/*.tsx"
5+
]
6+
}

Diff for: ‎test/fixtures/typescript/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"xo": {
3+
"space": 4
4+
}
5+
}

Diff for: ‎test/fixtures/typescript/two-spaces.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
console.log([
2+
2
3+
]);

Diff for: ‎test/lint-files.js

+20
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,23 @@ test('find configurations close to linted file', async t => {
167167
)
168168
);
169169
});
170+
171+
test('typescript files', async t => {
172+
const {results} = await fn.lintFiles('**/*', {cwd: 'fixtures/typescript'});
173+
174+
t.true(
175+
hasRule(
176+
results,
177+
path.resolve('fixtures/typescript/two-spaces.tsx'),
178+
'@typescript-eslint/indent'
179+
)
180+
);
181+
182+
t.true(
183+
hasRule(
184+
results,
185+
path.resolve('fixtures/typescript/child/extra-semicolon.ts'),
186+
'@typescript-eslint/no-extra-semi'
187+
)
188+
);
189+
});

Diff for: ‎test/lint-text.js

+10
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,13 @@ test('find configurations close to linted file', t => {
267267
]);\n`, {filename: 'fixtures/nested-configs/child-override/two-spaces.js'}));
268268
t.true(hasRule(results, 'indent'));
269269
});
270+
271+
test('typescript files', t => {
272+
let {results} = fn.lintText('console.log(\'extra-semicolon\');;\n', {filename: 'fixtures/typescript/child/extra-semicolon.ts'});
273+
t.true(hasRule(results, '@typescript-eslint/no-extra-semi'));
274+
275+
({results} = fn.lintText(`console.log([
276+
2
277+
]);`, {filename: 'fixtures/typescript/two-spaces.tsx'}));
278+
t.true(hasRule(results, '@typescript-eslint/indent'));
279+
});

Diff for: ‎test/options-manager.js

+137
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import path from 'path';
22
import test from 'ava';
3+
import omit from 'lodash/omit';
4+
import {readJson} from 'fs-extra';
35
import proxyquire from 'proxyquire';
46
import slash from 'slash';
57
import {DEFAULT_EXTENSION, DEFAULT_IGNORES} from '../lib/constants';
@@ -97,6 +99,35 @@ test('buildConfig: prettier: true', t => {
9799
t.is(config.rules['semi-spacing'], undefined);
98100
});
99101

102+
test('buildConfig: prettier: true, typescript file', t => {
103+
const config = manager.buildConfig({prettier: true, ts: true}, {});
104+
105+
t.deepEqual(config.plugins, ['prettier']);
106+
// Sets the `semi`, `useTabs` and `tabWidth` options in `prettier/prettier` based on the XO `space` and `semicolon` options
107+
// Sets `singleQuote`, `trailingComma`, `bracketSpacing` and `jsxBracketSameLine` with XO defaults
108+
t.deepEqual(config.rules['prettier/prettier'], ['error', {
109+
useTabs: true,
110+
bracketSpacing: false,
111+
jsxBracketSameLine: false,
112+
semi: true,
113+
singleQuote: true,
114+
tabWidth: 2,
115+
trailingComma: 'none'
116+
}]);
117+
118+
// eslint-prettier-config must always be last
119+
t.deepEqual(config.baseConfig.extends[config.baseConfig.extends.length - 1], 'prettier/@typescript-eslint');
120+
t.deepEqual(config.baseConfig.extends[config.baseConfig.extends.length - 2], 'xo-typescript');
121+
t.deepEqual(config.baseConfig.extends[config.baseConfig.extends.length - 3], 'prettier/unicorn');
122+
t.deepEqual(config.baseConfig.extends[config.baseConfig.extends.length - 4], 'prettier');
123+
// Indent rule is not enabled
124+
t.is(config.rules.indent, undefined);
125+
// Semi rule is not enabled
126+
t.is(config.rules.semi, undefined);
127+
// Semi-spacing is not enabled
128+
t.is(config.rules['semi-spacing'], undefined);
129+
});
130+
100131
test('buildConfig: prettier: true, semicolon: false', t => {
101132
const config = manager.buildConfig({prettier: true, semicolon: false}, {});
102133

@@ -372,6 +403,18 @@ test('buildConfig: extends', t => {
372403
]);
373404
});
374405

406+
test('buildConfig: typescript', t => {
407+
const config = manager.buildConfig({ts: true, tsConfigPath: './tsconfig.json'});
408+
409+
t.deepEqual(config.baseConfig.extends[config.baseConfig.extends.length - 1], 'xo-typescript');
410+
t.is(config.baseConfig.parser, require.resolve('@typescript-eslint/parser'));
411+
t.deepEqual(config.baseConfig.parserOptions, {
412+
warnOnUnsupportedTypeScriptVersion: false,
413+
ecmaFeatures: {jsx: true},
414+
project: './tsconfig.json'
415+
});
416+
});
417+
375418
test('findApplicableOverrides', t => {
376419
const result = manager.findApplicableOverrides('/user/dir/foo.js', [
377420
{files: '**/f*.js'},
@@ -436,6 +479,48 @@ test('mergeWithFileConfig: XO engine options false supersede package.json\'s', t
436479
t.deepEqual(options, expected);
437480
});
438481

482+
test('mergeWithFileConfig: typescript files', async t => {
483+
const cwd = path.resolve('fixtures', 'typescript', 'child');
484+
const filename = path.resolve(cwd, 'file.ts');
485+
const {options} = manager.mergeWithFileConfig({cwd, filename});
486+
const expected = {
487+
filename,
488+
extensions: DEFAULT_EXTENSION,
489+
ignores: DEFAULT_IGNORES,
490+
cwd,
491+
nodeVersion: undefined,
492+
semicolon: false,
493+
ts: true
494+
};
495+
t.deepEqual(omit(options, 'tsConfigPath'), expected);
496+
t.deepEqual(await readJson(options.tsConfigPath), {
497+
extends: path.resolve(cwd, 'tsconfig.json'),
498+
files: [path.resolve(cwd, 'file.ts')],
499+
include: [slash(path.resolve(cwd, '**/*.ts')), slash(path.resolve(cwd, '**/*.tsx'))]
500+
});
501+
});
502+
503+
test('mergeWithFileConfig: tsx files', async t => {
504+
const cwd = path.resolve('fixtures', 'typescript', 'child');
505+
const filename = path.resolve(cwd, 'file.tsx');
506+
const {options} = manager.mergeWithFileConfig({cwd, filename});
507+
const expected = {
508+
filename,
509+
extensions: DEFAULT_EXTENSION,
510+
ignores: DEFAULT_IGNORES,
511+
cwd,
512+
nodeVersion: undefined,
513+
semicolon: false,
514+
ts: true
515+
};
516+
t.deepEqual(omit(options, 'tsConfigPath'), expected);
517+
t.deepEqual(await readJson(options.tsConfigPath), {
518+
extends: path.resolve(cwd, 'tsconfig.json'),
519+
files: [path.resolve(cwd, 'file.tsx')],
520+
include: [slash(path.resolve(cwd, '**/*.ts')), slash(path.resolve(cwd, '**/*.tsx'))]
521+
});
522+
});
523+
439524
function mergeWithFileConfigFileType(t, {dir}) {
440525
const cwd = path.resolve('fixtures', 'config-files', dir);
441526
const {options} = manager.mergeWithFileConfig({cwd});
@@ -520,6 +605,58 @@ test('mergeWithFileConfigs: nested configs with prettier', async t => {
520605
]);
521606
});
522607

608+
test('mergeWithFileConfigs: typescript files', async t => {
609+
const cwd = path.resolve('fixtures', 'typescript');
610+
const paths = ['two-spaces.tsx', 'child/extra-semicolon.ts'];
611+
const result = await manager.mergeWithFileConfigs(paths, {cwd});
612+
613+
t.deepEqual(omit(result[0], 'options.tsConfigPath'), {
614+
files: [path.resolve(cwd, 'two-spaces.tsx')],
615+
options: {
616+
space: 4,
617+
nodeVersion: undefined,
618+
cwd,
619+
extensions: DEFAULT_EXTENSION,
620+
ignores: DEFAULT_IGNORES,
621+
ts: true
622+
},
623+
prettierOptions: {}
624+
});
625+
t.deepEqual(await readJson(result[0].options.tsConfigPath), {
626+
files: [path.resolve(cwd, 'two-spaces.tsx')],
627+
compilerOptions: {
628+
newLine: 'lf',
629+
noFallthroughCasesInSwitch: true,
630+
noImplicitReturns: true,
631+
noUnusedLocals: true,
632+
noUnusedParameters: true,
633+
strict: true,
634+
target: 'es2018'
635+
}
636+
});
637+
638+
t.deepEqual(omit(result[1], 'options.tsConfigPath'), {
639+
files: [path.resolve(cwd, 'child/extra-semicolon.ts')],
640+
options: {
641+
semicolon: false,
642+
nodeVersion: undefined,
643+
cwd: path.resolve(cwd, 'child'),
644+
extensions: DEFAULT_EXTENSION,
645+
ignores: DEFAULT_IGNORES,
646+
ts: true
647+
},
648+
prettierOptions: {}
649+
});
650+
t.deepEqual(await readJson(result[1].options.tsConfigPath), {
651+
extends: path.resolve(cwd, 'child/tsconfig.json'),
652+
files: [path.resolve(cwd, 'child/extra-semicolon.ts')],
653+
include: [
654+
slash(path.resolve(cwd, 'child/**/*.ts')),
655+
slash(path.resolve(cwd, 'child/**/*.tsx'))
656+
]
657+
});
658+
});
659+
523660
async function mergeWithFileConfigsFileType(t, {dir}) {
524661
const cwd = path.resolve('fixtures', 'config-files', dir);
525662
const paths = ['a.js', 'b.js'];

0 commit comments

Comments
 (0)
Please sign in to comment.