Skip to content

Commit 82cbe41

Browse files
committedMar 19, 2019
Add support for multiple files - fixes #14
1 parent cc9fec4 commit 82cbe41

File tree

16 files changed

+170
-75
lines changed

16 files changed

+170
-75
lines changed
 

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040
],
4141
"dependencies": {
4242
"eslint-formatter-pretty": "^1.3.0",
43+
"globby": "^9.1.0",
4344
"meow": "^5.0.0",
4445
"path-exists": "^3.0.0",
45-
"pkg-conf": "^3.0.0",
4646
"read-pkg-up": "^4.0.0",
4747
"typescript": "^3.0.1",
4848
"update-notifier": "^2.5.0"

‎readme.md

+15
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ expectType<string>(await concat('foo', 'bar'));
7676
expectError(await concat(true, false));
7777
```
7878

79+
### Test directory
80+
81+
When you have spread your tests over multiple files, you can store all those files in a test directory called `test-d`. If you want to use another directory name, you can change it in `package.json`.
82+
83+
```json
84+
{
85+
"name": "my-module",
86+
"tsd-check": {
87+
"directory": "my-test-dir"
88+
}
89+
}
90+
```
91+
92+
Now you can put all your test files in the `my-test-dir` directory.
93+
7994

8095
## Assertions
8196

‎source/lib/compiler.ts

+37-66
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,76 @@
11
import * as path from 'path';
2-
import * as pkgConf from 'pkg-conf';
3-
import {
4-
ScriptTarget,
5-
ModuleResolutionKind,
6-
flattenDiagnosticMessageText,
7-
CompilerOptions,
8-
createProgram,
9-
JsxEmit,
10-
SyntaxKind,
11-
SourceFile,
12-
Diagnostic as TSDiagnostic
13-
} from 'typescript';
14-
import {Diagnostic, DiagnosticCode, Context, Position} from './interfaces';
2+
import {flattenDiagnosticMessageText, createProgram, SyntaxKind, Diagnostic as TSDiagnostic, Program, SourceFile} from 'typescript';
3+
import {Diagnostic, DiagnosticCode, Context, Location} from './interfaces';
154

165
// List of diagnostic codes that should be ignored
176
const ignoredDiagnostics = new Set<number>([
187
DiagnosticCode.AwaitIsOnlyAllowedInAsyncFunction
198
]);
209

21-
const loadConfig = (cwd: string): CompilerOptions => {
22-
const config = pkgConf.sync('tsd-check', {
23-
cwd,
24-
defaults: {
25-
compilerOptions: {
26-
strict: true,
27-
jsx: JsxEmit.React,
28-
target: ScriptTarget.ES2017
29-
}
30-
}
31-
});
32-
33-
return {
34-
...config.compilerOptions,
35-
...{
36-
moduleResolution: ModuleResolutionKind.NodeJs,
37-
skipLibCheck: true
38-
}
39-
};
40-
};
10+
const diagnosticCodesToIgnore = new Set<DiagnosticCode>([
11+
DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType,
12+
DiagnosticCode.PropertyDoesNotExistOnType,
13+
DiagnosticCode.CannotAssignToReadOnlyProperty
14+
]);
4115

4216
/**
4317
* Extract all the `expectError` statements and convert it to a range map.
4418
*
45-
* @param sourceFile - File to extract the statements from.
19+
* @param program - The TypeScript program.
4620
*/
47-
const extractExpectErrorRanges = (sourceFile: SourceFile) => {
48-
const expectedErrors = new Map<Position, Pick<Diagnostic, 'fileName' | 'line' | 'column'>>();
21+
const extractExpectErrorRanges = (program: Program) => {
22+
const expectedErrors = new Map<Location, Pick<Diagnostic, 'fileName' | 'line' | 'column'>>();
4923

50-
for (const statement of sourceFile.statements) {
51-
if (statement.kind !== SyntaxKind.ExpressionStatement || !statement.getText().startsWith('expectError')) {
52-
continue;
53-
}
24+
for (const sourceFile of program.getSourceFiles()) {
25+
for (const statement of sourceFile.statements) {
26+
if (statement.kind !== SyntaxKind.ExpressionStatement || !statement.getText().startsWith('expectError')) {
27+
continue;
28+
}
5429

55-
const position = {
56-
start: statement.getStart(),
57-
end: statement.getEnd()
58-
};
30+
const location = {
31+
fileName: statement.getSourceFile().fileName,
32+
start: statement.getStart(),
33+
end: statement.getEnd()
34+
};
5935

60-
const pos = statement.getSourceFile().getLineAndCharacterOfPosition(statement.getStart());
36+
const pos = statement.getSourceFile().getLineAndCharacterOfPosition(statement.getStart());
6137

62-
expectedErrors.set(position, {
63-
fileName: statement.getSourceFile().fileName,
64-
line: pos.line + 1,
65-
column: pos.character
66-
});
38+
expectedErrors.set(location, {
39+
fileName: location.fileName,
40+
line: pos.line + 1,
41+
column: pos.character
42+
});
43+
}
6744
}
6845

6946
return expectedErrors;
7047
};
7148

72-
const diagnosticCodesToIgnore = [
73-
DiagnosticCode.ArgumentTypeIsNotAssignableToParameterType,
74-
DiagnosticCode.PropertyDoesNotExistOnType,
75-
DiagnosticCode.CannotAssignToReadOnlyProperty
76-
];
77-
7849
/**
7950
* Check if the provided diagnostic should be ignored.
8051
*
8152
* @param diagnostic - The diagnostic to validate.
8253
* @param expectedErrors - Map of the expected errors.
8354
* @return Boolean indicating if the diagnostic should be ignored or not.
8455
*/
85-
const ignoreDiagnostic = (diagnostic: TSDiagnostic, expectedErrors: Map<Position, any>): boolean => {
56+
const ignoreDiagnostic = (diagnostic: TSDiagnostic, expectedErrors: Map<Location, any>): boolean => {
8657
if (ignoredDiagnostics.has(diagnostic.code)) {
8758
// Filter out diagnostics which are present in the `ignoredDiagnostics` set
8859
return true;
8960
}
9061

91-
if (!diagnosticCodesToIgnore.includes(diagnostic.code)) {
62+
if (!diagnosticCodesToIgnore.has(diagnostic.code)) {
9263
return false;
9364
}
9465

95-
for (const [range] of expectedErrors) {
66+
const diagnosticFileName = (diagnostic.file as SourceFile).fileName;
67+
68+
for (const [location] of expectedErrors) {
9669
const start = diagnostic.start as number;
9770

98-
if (start > range.start && start < range.end) {
71+
if (diagnosticFileName === location.fileName && start > location.start && start < location.end) {
9972
// Remove the expected error from the Map so it's not being reported as failure
100-
expectedErrors.delete(range);
73+
expectedErrors.delete(location);
10174
return true;
10275
}
10376
}
@@ -112,19 +85,17 @@ const ignoreDiagnostic = (diagnostic: TSDiagnostic, expectedErrors: Map<Position
11285
* @returns List of diagnostics
11386
*/
11487
export const getDiagnostics = (context: Context): Diagnostic[] => {
115-
const compilerOptions = loadConfig(context.cwd);
116-
117-
const fileName = path.join(context.cwd, context.testFile);
88+
const fileNames = context.testFiles.map(fileName => path.join(context.cwd, fileName));
11889

11990
const result: Diagnostic[] = [];
12091

121-
const program = createProgram([fileName], compilerOptions);
92+
const program = createProgram(fileNames, context.config.compilerOptions);
12293

12394
const diagnostics = program
12495
.getSemanticDiagnostics()
12596
.concat(program.getSyntacticDiagnostics());
12697

127-
const expectedErrors = extractExpectErrorRanges(program.getSourceFile(fileName) as SourceFile);
98+
const expectedErrors = extractExpectErrorRanges(program);
12899

129100
for (const diagnostic of diagnostics) {
130101
if (!diagnostic.file || ignoreDiagnostic(diagnostic, expectedErrors)) {

‎source/lib/config.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {JsxEmit, ScriptTarget, ModuleResolutionKind} from 'typescript';
2+
import {Config} from './interfaces';
3+
4+
/**
5+
* Load the configuration settings.
6+
*
7+
* @param pkg - The package.json object.
8+
* @returns The config object.
9+
*/
10+
export default (pkg: any): Config => {
11+
const config = {
12+
directory: 'test-d',
13+
compilerOptions: {
14+
strict: true,
15+
jsx: JsxEmit.React,
16+
target: ScriptTarget.ES2017
17+
},
18+
...pkg['tsd-check']
19+
};
20+
21+
return {
22+
...config,
23+
compilerOptions: {
24+
...config.compilerOptions,
25+
...{
26+
moduleResolution: ModuleResolutionKind.NodeJs,
27+
skipLibCheck: true
28+
}
29+
}
30+
};
31+
};

‎source/lib/index.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as path from 'path';
22
import * as readPkgUp from 'read-pkg-up';
33
import * as pathExists from 'path-exists';
4+
import globby from 'globby';
45
import {getDiagnostics as getTSDiagnostics} from './compiler';
6+
import loadConfig from './config';
57
import getCustomDiagnostics from './rules';
6-
import {Context} from './interfaces';
8+
import {Context, Config} from './interfaces';
79

810
interface Options {
911
cwd: string;
@@ -21,16 +23,24 @@ const findTypingsFile = async (pkg: any, options: Options) => {
2123
return typings;
2224
};
2325

24-
const findTestFile = async (typingsFile: string, options: Options) => {
26+
const findTestFiles = async (typingsFile: string, options: Options & {config: Config}) => {
2527
const testFile = typingsFile.replace(/\.d\.ts$/, '.test-d.ts');
28+
const testDir = options.config.directory;
2629

2730
const testFileExists = await pathExists(path.join(options.cwd, testFile));
31+
const testDirExists = await pathExists(path.join(options.cwd, testDir));
2832

29-
if (!testFileExists) {
33+
if (!testFileExists && !testDirExists) {
3034
throw new Error(`The test file \`${testFile}\` does not exist. Create one and try again.`);
3135
}
3236

33-
return testFile;
37+
let testFiles = [testFile];
38+
39+
if (!testFileExists) {
40+
testFiles = await globby(`${testDir}/**/*.ts`, {cwd: options.cwd});
41+
}
42+
43+
return testFiles;
3444
};
3545

3646
/**
@@ -45,16 +55,22 @@ export default async (options: Options = {cwd: process.cwd()}) => {
4555
throw new Error('No `package.json` file found. Make sure you are running the command in a Node.js project.');
4656
}
4757

58+
const config = loadConfig(pkg);
59+
4860
// Look for a typings file, otherwise use `index.d.ts` in the root directory. If the file is not found, throw an error.
4961
const typingsFile = await findTypingsFile(pkg, options);
5062

51-
const testFile = await findTestFile(typingsFile, options);
63+
const testFiles = await findTestFiles(typingsFile, {
64+
...options,
65+
config
66+
});
5267

5368
const context: Context = {
5469
cwd: options.cwd,
5570
pkg,
5671
typingsFile,
57-
testFile
72+
testFiles,
73+
config
5874
};
5975

6076
return [

‎source/lib/interfaces.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
import {CompilerOptions} from 'typescript';
2+
3+
export interface Config {
4+
directory: string;
5+
compilerOptions: CompilerOptions;
6+
}
7+
18
export interface Context {
29
cwd: string;
310
pkg: any;
411
typingsFile: string;
5-
testFile: string;
12+
testFiles: string[];
13+
config: Config;
614
}
715

816
export enum DiagnosticCode {
@@ -20,7 +28,8 @@ export interface Diagnostic {
2028
column?: number;
2129
}
2230

23-
export interface Position {
31+
export interface Location {
32+
fileName: string;
2433
start: number;
2534
end: number;
2635
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare const one: {
2+
(foo: string, bar: string): string;
3+
(foo: number, bar: number): number;
4+
};
5+
6+
export default one;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports.default = (foo, bar) => {
2+
return foo + bar;
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {expectError} from '../../../../..';
2+
import one from '..';
3+
4+
expectError(one(1, 2));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
declare const one: {
2+
(foo: string, bar: string): string;
3+
(foo: number, bar: number): number;
4+
};
5+
6+
export default one;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports.default = (foo, bar) => {
2+
return foo + bar;
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "foo",
3+
"types": "index.d.ts"
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {expectType} from '../../../../..';
2+
import one from '..';
3+
4+
expectType<number>(one(1, 2));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {expectType} from '../../../../..';
2+
import one from '..';
3+
4+
expectType<string>(one('foo', 'bar'));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {expectError} from '../../../../..';
2+
import one from '..';
3+
4+
expectError(one(true, false));

‎source/test/test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,21 @@ test('support top-level await', async t => {
112112
t.true(diagnostics.length === 0);
113113
});
114114

115+
test('support default test directory', async t => {
116+
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-directory/default')});
117+
118+
t.true(diagnostics.length === 0);
119+
});
120+
121+
test('support setting a custom test directory', async t => {
122+
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/test-directory/custom')});
123+
124+
t.true(diagnostics[0].column === 0);
125+
t.true(diagnostics[0].line === 4);
126+
t.true(diagnostics[0].message === 'Expected an error, but found none.');
127+
t.true(diagnostics[0].severity === 'error');
128+
});
129+
115130
test('expectError for functions', async t => {
116131
const diagnostics = await m({cwd: path.join(__dirname, 'fixtures/expect-error/functions')});
117132

0 commit comments

Comments
 (0)
Please sign in to comment.