Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: avajs/typescript
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.0.1
Choose a base ref
...
head repository: avajs/typescript
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.0.0
Choose a head ref
  • 7 commits
  • 23 files changed
  • 1 contributor

Commits on Mar 12, 2023

  1. Support .cts and .mts

    novemberborn committed Mar 12, 2023
    Copy the full SHA
    4ec638c View commit details
  2. Copy the full SHA
    0e25160 View commit details
  3. Copy the full SHA
    425198a View commit details
  4. Copy the full SHA
    dfdfea5 View commit details
  5. Update dependencies

    novemberborn committed Mar 12, 2023
    Copy the full SHA
    75aad0a View commit details
  6. Update GHA workflow

    novemberborn committed Mar 12, 2023
    Copy the full SHA
    430d173 View commit details
  7. 4.0.0

    novemberborn committed Mar 12, 2023
    Copy the full SHA
    e0a5cf0 View commit details
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
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:
@@ -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

@@ -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
@@ -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`;
@@ -83,7 +83,7 @@ export default function typescriptProvider({negotiateProtocol}) {
validate(config, configProperties);

const {
extensions = ['ts'],
extensions = ['ts', 'cts', 'mts'],
rewritePaths: relativeRewritePaths,
compile,
} = config;
@@ -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;
}
@@ -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}) {
@@ -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 {
@@ -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);
},
};
},
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@ava/typescript",
"version": "3.0.1",
"version": "4.0.0",
"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"
@@ -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": [
@@ -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 => ({
@@ -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')},
@@ -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')},
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": [
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
@@ -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) => {
@@ -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'));
});
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