Skip to content

Support additional extensions and other modernizations #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [^12.22, ^14.17, ^16.4, ^17]
node-version: [^14.19, ^16.15, ^18]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install --global npm@8
- name: Install npm@8 for Node.js 14
if: matrix.node-version == '^14.19'
run: npm install --global npm@^8
- run: npm install --no-audit
- run: npm test
- uses: codecov/codecov-action@v2
Expand Down
16 changes: 4 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
# @ava/typescript

Adds [TypeScript](https://www.typescriptlang.org/) support to [AVA 4](https://avajs.dev).
Adds [TypeScript](https://www.typescriptlang.org/) support to [AVA](https://avajs.dev).

This is designed to work for projects that precompile TypeScript. It allows AVA to load the compiled JavaScript, while configuring AVA to treat the TypeScript files as test files.

In other words, say you have a test file at `src/test.ts`. You've configured TypeScript to output to `build/`. Using `@ava/typescript` you can run the test using `npx ava src/test.ts`.

## For AVA 3 users

Use version 2:

```console
npm install --save-dev @ava/typescript@2
```

Note that v2 does not support ES modules. This requires v3 and AVA 4.

## Enabling TypeScript support

Add this package to your project:
Expand Down Expand Up @@ -47,7 +37,7 @@ You can enable compilation via the `compile` property. If `false`, AVA will assu

Output files are expected to have the `.js` extension.

AVA searches your entire project for `*.js`, `*.cjs`, `*.mjs` and `*.ts` files (or other extensions you've configured). It will ignore such files found in the `rewritePaths` targets (e.g. `build/`). If you use more specific paths, for instance `build/main/`, you may need to change AVA's `files` configuration to ignore other directories.
AVA searches your entire project for `*.js`, `*.cjs`, `*.mjs`, `*.ts`, `*.cts` and `*.mts` files (or other extensions you've configured). It will ignore such files found in the `rewritePaths` targets (e.g. `build/`). If you use more specific paths, for instance `build/main/`, you may need to change AVA's `files` configuration to ignore other directories.

## ES Modules

Expand Down Expand Up @@ -75,6 +65,8 @@ You can configure AVA to recognize additional file extensions. To add (partial
}
```

If you use the [`allowJs` TypeScript option](https://www.typescriptlang.org/tsconfig/allowJs.html) you'll have to specify the `js`, `cjs` and `mjs` extensions for them to be rewritten.

See also AVA's [`extensions` option](https://github.com/avajs/ava/blob/master/docs/06-configuration.md#options).

† Note that the [*preserve* mode for JSX](https://www.typescriptlang.org/docs/handbook/jsx.html) is not (yet) supported.
40 changes: 30 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import {pathToFileURL} from 'node:url';
import escapeStringRegexp from 'escape-string-regexp';
import execa from 'execa';
import {execa} from 'execa';

const pkg = JSON.parse(fs.readFileSync(new URL('package.json', import.meta.url)));
const help = `See https://github.com/avajs/typescript/blob/v${pkg.version}/README.md`;
Expand Down Expand Up @@ -83,7 +83,7 @@ export default function typescriptProvider({negotiateProtocol}) {
validate(config, configProperties);

const {
extensions = ['ts'],
extensions = ['ts', 'cts', 'mts'],
rewritePaths: relativeRewritePaths,
compile,
} = config;
Expand Down Expand Up @@ -118,7 +118,7 @@ export default function typescriptProvider({negotiateProtocol}) {
return rewritePaths.some(([from]) => filePath.startsWith(from));
},

resolveTestFile(testfile) {
resolveTestFile(testfile) { // Used under AVA 3.2 protocol by legacy watcher implementation.
if (!testFileExtension.test(testfile)) {
return testfile;
}
Expand All @@ -129,8 +129,14 @@ export default function typescriptProvider({negotiateProtocol}) {
}

const [from, to] = rewrite;
// TODO: Support JSX preserve mode — https://www.typescriptlang.org/docs/handbook/jsx.html
return `${to}${testfile.slice(from.length)}`.replace(testFileExtension, '.js');
let newExtension = '.js';
if (testfile.endsWith('.cts')) {
newExtension = '.cjs';
} else if (testfile.endsWith('.mts')) {
newExtension = '.mjs';
}

return `${to}${testfile.slice(from.length)}`.replace(testFileExtension, newExtension);
},

updateGlobs({filePatterns, ignoredByWatcherPatterns}) {
Expand All @@ -142,15 +148,19 @@ export default function typescriptProvider({negotiateProtocol}) {
],
ignoredByWatcherPatterns: [
...ignoredByWatcherPatterns,
...Object.values(relativeRewritePaths).map(to => `${to}**/*.js.map`),
...Object.values(relativeRewritePaths).flatMap(to => [
`${to}**/*.js.map`,
`${to}**/*.cjs.map`,
`${to}**/*.mjs.map`,
]),
],
};
},
};
},

worker({extensionsToLoadAsModules, state: {extensions, rewritePaths}}) {
const useImport = extensionsToLoadAsModules.includes('js');
const importJs = extensionsToLoadAsModules.includes('js');
const testFileExtension = new RegExp(`\\.(${extensions.map(ext => escapeStringRegexp(ext)).join('|')})$`);

return {
Expand All @@ -160,9 +170,19 @@ export default function typescriptProvider({negotiateProtocol}) {

async load(ref, {requireFn}) {
const [from, to] = rewritePaths.find(([from]) => ref.startsWith(from));
// TODO: Support JSX preserve mode — https://www.typescriptlang.org/docs/handbook/jsx.html
const rewritten = `${to}${ref.slice(from.length)}`.replace(testFileExtension, '.js');
return useImport ? import(pathToFileURL(rewritten)) : requireFn(rewritten); // eslint-disable-line node/no-unsupported-features/es-syntax
let rewritten = `${to}${ref.slice(from.length)}`;
let useImport = true;
if (ref.endsWith('.cts')) {
rewritten = rewritten.replace(/\.cts$/, '.cjs');
useImport = false;
} else if (ref.endsWith('.mts')) {
rewritten = rewritten.replace(/\.mts$/, '.mjs');
} else {
rewritten = rewritten.replace(testFileExtension, '.js');
useImport = importJs;
}

return useImport ? import(pathToFileURL(rewritten)) : requireFn(rewritten);
},
};
},
Expand Down
17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "3.0.1",
"description": "TypeScript provider for AVA",
"engines": {
"node": ">=12.22 <13 || >=14.17 <15 || >=16.4 <17 || >=17"
"node": ">=14.19 <15 || >=16.15 <17 || >=18"
},
"files": [
"index.js"
Expand All @@ -24,14 +24,14 @@
},
"dependencies": {
"escape-string-regexp": "^5.0.0",
"execa": "^5.1.1"
"execa": "^7.1.0"
},
"devDependencies": {
"ava": "4.0.0-rc.1",
"c8": "^7.10.0",
"del": "^6.0.0",
"typescript": "^4.4.4",
"xo": "^0.46.3"
"ava": "^5.2.0",
"c8": "^7.13.0",
"del": "^7.0.0",
"typescript": "^4.9.5",
"xo": "^0.53.1"
},
"c8": {
"reporter": [
Expand All @@ -52,7 +52,8 @@
},
"xo": {
"ignores": [
"test/broken-fixtures"
"test/broken-fixtures",
"test/fixtures/**/compiled/**"
]
}
}
12 changes: 6 additions & 6 deletions test/compilation.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import test from 'ava';
import del from 'del';
import execa from 'execa';
import {deleteAsync} from 'del';
import {execaNode} from 'execa';
import createProviderMacro from './_with-provider.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const withProvider = createProviderMacro('ava-3.2', '3.2.0', path.join(__dirname, 'fixtures'));
const withAltProvider = createProviderMacro('ava-3.2', '3.2.0', path.join(__dirname, 'broken-fixtures'));

test.before('deleting compiled files', async t => {
t.log(await del('test/fixtures/typescript/compiled'));
t.log(await del('test/broken-fixtures/typescript/compiled'));
t.log(await deleteAsync('test/fixtures/typescript/compiled'));
t.log(await deleteAsync('test/broken-fixtures/typescript/compiled'));
});

const compile = async provider => ({
Expand All @@ -28,7 +28,7 @@ const compile = async provider => ({

test('worker(): load rewritten paths files', withProvider, async (t, provider) => {
const {state} = await compile(provider);
const {stdout, stderr} = await execa.node(
const {stdout, stderr} = await execaNode(
path.join(__dirname, 'fixtures/install-and-load'),
[JSON.stringify({state}), path.join(__dirname, 'fixtures/ts', 'file.ts')],
{cwd: path.join(__dirname, 'fixtures')},
Expand All @@ -42,7 +42,7 @@ test('worker(): load rewritten paths files', withProvider, async (t, provider) =

test('worker(): runs compiled files', withProvider, async (t, provider) => {
const {state} = await compile(provider);
const {stdout, stderr} = await execa.node(
const {stdout, stderr} = await execaNode(
path.join(__dirname, 'fixtures/install-and-load'),
[JSON.stringify({state}), path.join(__dirname, 'fixtures/compiled', 'index.ts')],
{cwd: path.join(__dirname, 'fixtures')},
Expand Down
33 changes: 0 additions & 33 deletions test/esm.js

This file was deleted.

1 change: 0 additions & 1 deletion test/fixtures/esm/index.js

This file was deleted.

1 change: 0 additions & 1 deletion test/fixtures/esm/index.ts

This file was deleted.

3 changes: 3 additions & 0 deletions test/fixtures/load/compiled/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
console.log('logged in fixtures/load/index.cts');
2 changes: 2 additions & 0 deletions test/fixtures/load/compiled/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('logged in fixtures/load/index.ts');
export {};
2 changes: 2 additions & 0 deletions test/fixtures/load/compiled/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log('logged in fixtures/load/index.mts');
export {};
1 change: 1 addition & 0 deletions test/fixtures/load/index.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('logged in fixtures/load/index.cts');
1 change: 1 addition & 0 deletions test/fixtures/load/index.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('logged in fixtures/load/index.mts');
1 change: 1 addition & 0 deletions test/fixtures/load/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('logged in fixtures/load/index.ts');
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"module": "Node16",
"outDir": "compiled"
},
"include": [
Expand Down
61 changes: 61 additions & 0 deletions test/load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import test from 'ava';
import {execaNode} from 'execa';
import createProviderMacro from './_with-provider.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const withProvider = createProviderMacro('ava-3.2', '3.2.0', path.join(__dirname, 'fixtures'));

const setup = async provider => ({
state: await provider.main({
config: {
rewritePaths: {
'load/': 'load/compiled/',
},
compile: false,
},
}).compile(),
});

test('worker(): load .cts', withProvider, async (t, provider) => {
const {state} = await setup(provider);
const {stdout, stderr} = await execaNode(
path.join(__dirname, 'fixtures/install-and-load'),
[JSON.stringify({state}), path.join(__dirname, 'fixtures/load', 'index.cts')],
{cwd: path.join(__dirname, 'fixtures')},
);
if (stderr.length > 0) {
t.log(stderr);
}

t.snapshot(stdout);
});

test('worker(): load .mts', withProvider, async (t, provider) => {
const {state} = await setup(provider);
const {stdout, stderr} = await execaNode(
path.join(__dirname, 'fixtures/install-and-load'),
[JSON.stringify({state}), path.join(__dirname, 'fixtures/load', 'index.mts')],
{cwd: path.join(__dirname, 'fixtures')},
);
if (stderr.length > 0) {
t.log(stderr);
}

t.snapshot(stdout);
});

test('worker(): load .ts', withProvider, async (t, provider) => {
const {state} = await setup(provider);
const {stdout, stderr} = await execaNode(
path.join(__dirname, 'fixtures/install-and-load'),
[JSON.stringify({extensionsToLoadAsModules: ['js'], state}), path.join(__dirname, 'fixtures/load', 'index.ts')],
{cwd: path.join(__dirname, 'fixtures')},
);
if (stderr.length > 0) {
t.log(stderr);
}

t.snapshot(stdout);
});
6 changes: 4 additions & 2 deletions test/protocol-ava-3.2.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ test('main() config validation: rewrite paths must end in a /', withProvider, (t
validateConfig(t, provider, {rewritePaths: {'src/': 'build', compile: false}});
});

test('main() extensions: defaults to [\'ts\']', withProvider, (t, provider) => {
t.deepEqual(provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}}).extensions, ['ts']);
test('main() extensions: defaults to [\'ts\', \'cts\', \'mts\']', withProvider, (t, provider) => {
t.deepEqual(provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}}).extensions, ['ts', 'cts', 'mts']);
});

test('main() extensions: returns configured extensions', withProvider, (t, provider) => {
Expand All @@ -76,6 +76,8 @@ test('main() ignoreChange()', withProvider, (t, provider) => {
test('main() resolveTestfile()', withProvider, (t, provider) => {
const main = provider.main({config: {rewritePaths: {'src/': 'build/'}, compile: false}});
t.is(main.resolveTestFile(path.join(__dirname, 'src/foo.ts')), path.join(__dirname, 'build/foo.js'));
t.is(main.resolveTestFile(path.join(__dirname, 'src/foo.cts')), path.join(__dirname, 'build/foo.cjs'));
t.is(main.resolveTestFile(path.join(__dirname, 'src/foo.mts')), path.join(__dirname, 'build/foo.mjs'));
t.is(main.resolveTestFile(path.join(__dirname, 'build/foo.js')), path.join(__dirname, 'build/foo.js'));
t.is(main.resolveTestFile(path.join(__dirname, 'foo/bar.ts')), path.join(__dirname, 'foo/bar.ts'));
});
Expand Down
11 changes: 0 additions & 11 deletions test/snapshots/esm.js.md

This file was deleted.

Binary file removed test/snapshots/esm.js.snap
Binary file not shown.
Loading