Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: run eslint with type information on CI #13368

Merged
merged 13 commits into from Oct 3, 2022
2 changes: 2 additions & 0 deletions .github/workflows/nodejs.yml
Expand Up @@ -54,6 +54,8 @@ jobs:
run: yarn typecheck:examples
- name: typecheck tests
run: yarn typecheck:tests
- name: run ESLint with type info
run: yarn lint-ts-files

lint:
name: Lint
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -103,6 +103,7 @@
"lint:prettier-script": "prettier . \"!**/*.{js,jsx,cjs,mjs,ts,tsx}\" --cache",
"lint:prettier": "yarn lint:prettier-script --write",
"lint:prettier:ci": "yarn lint:prettier-script --check",
"lint-ts-files": "node scripts/lintTs.mjs",
"remove-examples": "node ./scripts/remove-examples.mjs",
"test-ci-partial": "yarn test-ci-partial:parallel -i",
"test-ci-partial:parallel": "yarn jest --color --config jest.config.ci.mjs",
Expand Down
18 changes: 11 additions & 7 deletions packages/babel-jest/src/__tests__/getCacheKey.test.ts
Expand Up @@ -52,7 +52,8 @@ describe('getCacheKey', () => {
readFileSync: () => 'new this file',
}));

const {createTransformer}: typeof import('../index') = require('../index');
const {createTransformer} =
require('../index') as typeof import('../index');

const newCacheKey = createTransformer().getCacheKey!(
sourceText,
Expand All @@ -65,7 +66,7 @@ describe('getCacheKey', () => {

test('if `babelOptions.options` value is changing', () => {
jest.doMock('../loadBabelConfig', () => {
const babel: typeof import('@babel/core') = require('@babel/core');
const babel = require('@babel/core') as typeof import('@babel/core');

return {
loadPartialConfig: (options: BabelTransformOptions) => ({
Expand All @@ -75,7 +76,8 @@ describe('getCacheKey', () => {
};
});

const {createTransformer}: typeof import('../index') = require('../index');
const {createTransformer} =
require('../index') as typeof import('../index');

const newCacheKey = createTransformer().getCacheKey!(
sourceText,
Expand Down Expand Up @@ -117,7 +119,7 @@ describe('getCacheKey', () => {

test('if `babelOptions.config` value is changing', () => {
jest.doMock('../loadBabelConfig', () => {
const babel: typeof import('@babel/core') = require('@babel/core');
const babel = require('@babel/core') as typeof import('@babel/core');

return {
loadPartialConfig: (options: BabelTransformOptions) => ({
Expand All @@ -127,7 +129,8 @@ describe('getCacheKey', () => {
};
});

const {createTransformer}: typeof import('../index') = require('../index');
const {createTransformer} =
require('../index') as typeof import('../index');

const newCacheKey = createTransformer().getCacheKey!(
sourceText,
Expand All @@ -140,7 +143,7 @@ describe('getCacheKey', () => {

test('if `babelOptions.babelrc` value is changing', () => {
jest.doMock('../loadBabelConfig', () => {
const babel: typeof import('@babel/core') = require('@babel/core');
const babel = require('@babel/core') as typeof import('@babel/core');

return {
loadPartialConfig: (options: BabelTransformOptions) => ({
Expand All @@ -150,7 +153,8 @@ describe('getCacheKey', () => {
};
});

const {createTransformer}: typeof import('../index') = require('../index');
const {createTransformer} =
require('../index') as typeof import('../index');

const newCacheKey = createTransformer().getCacheKey!(
sourceText,
Expand Down
25 changes: 19 additions & 6 deletions packages/babel-jest/src/__tests__/index.ts
Expand Up @@ -13,14 +13,17 @@ import {makeProjectConfig} from '@jest/test-utils';
import type {TransformOptions} from '@jest/transform';
import babelJest, {createTransformer} from '../index';
import {loadPartialConfig} from '../loadBabelConfig';

jest.mock('../loadBabelConfig', () => {
const actual =
jest.requireActual<typeof import('@babel/core')>('@babel/core');

return {
loadPartialConfig: jest.fn((...args) => actual.loadPartialConfig(...args)),
loadPartialConfigAsync: jest.fn((...args) =>
actual.loadPartialConfigAsync(...args),
loadPartialConfig: jest.fn<typeof actual.loadPartialConfig>((...args) =>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#13367 came from this 馃檪

actual.loadPartialConfig(...args),
),
loadPartialConfigAsync: jest.fn<typeof actual.loadPartialConfigAsync>(
(...args) => actual.loadPartialConfigAsync(...args),
),
};
});
Expand Down Expand Up @@ -69,7 +72,7 @@ test('Returns source string with inline maps when no transformOptions is passed'
});

test('Returns source string with inline maps when no transformOptions is passed async', async () => {
const result: any = await defaultBabelJestTransformer.processAsync!(
const result = await defaultBabelJestTransformer.processAsync!(
sourceString,
'dummy_path.js',
{
Expand All @@ -86,8 +89,18 @@ test('Returns source string with inline maps when no transformOptions is passed
expect(result.map).toBeDefined();
expect(result.code).toMatch('//# sourceMappingURL');
expect(result.code).toMatch('customMultiply');
expect(result.map!.sources).toEqual(['dummy_path.js']);
expect(JSON.stringify(result.map!.sourcesContent)).toMatch('customMultiply');

const {map} = result;

expect(map).toBeTruthy();
expect(typeof map).not.toBe('string');

if (map == null || typeof map === 'string') {
throw new Error('dead code');
}

expect(map.sources).toEqual(['dummy_path.js']);
expect(JSON.stringify(map.sourcesContent)).toMatch('customMultiply');
});

describe('caller option correctly merges from defaults and options', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-plugin-jest-hoist/src/index.ts
Expand Up @@ -114,10 +114,10 @@ const IDVisitor = {
],
};

const FUNCTIONS: Record<
const FUNCTIONS = Object.create(null) as Record<
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object.create returns any

string,
<T extends Node>(args: Array<NodePath<T>>) => boolean
> = Object.create(null);
>;

FUNCTIONS.mock = args => {
if (args.length === 1) {
Expand Down
9 changes: 6 additions & 3 deletions packages/diff-sequences/src/__tests__/index.test.ts
Expand Up @@ -15,7 +15,8 @@ describe('invalid arg', () => {
describe('length', () => {
test('is not a number', () => {
expect(() => {
diff('0' as any, 0, isCommon, foundSubsequence);
// @ts-expect-error: Testing runtime errors here
diff('0', 0, isCommon, foundSubsequence);
}).toThrow(/aLength/);
});
test('Infinity is not a safe integer', () => {
Expand Down Expand Up @@ -49,12 +50,14 @@ describe('invalid arg', () => {
describe('callback', () => {
test('null is not a function', () => {
expect(() => {
diff(0, 0, null as any, foundSubsequence);
// @ts-expect-error: Testing runtime errors here
diff(0, 0, null, foundSubsequence);
}).toThrow(/isCommon/);
});
test('undefined is not a function', () => {
expect(() => {
diff(0, 0, isCommon, undefined as any);
// @ts-expect-error: Testing runtime errors here
diff(0, 0, isCommon, undefined);
}).toThrow(/foundSubsequence/);
});
});
Expand Down
8 changes: 2 additions & 6 deletions packages/expect/src/index.ts
Expand Up @@ -39,7 +39,6 @@ import toThrowMatchers, {
createMatcher as createThrowMatcher,
} from './toThrowMatchers';
import type {
AsyncExpectationResult,
Expect,
ExpectationResult,
MatcherContext,
Expand Down Expand Up @@ -363,19 +362,16 @@ const makeThrowingMatcher = (
})();

if (isPromise(potentialResult)) {
const asyncResult = potentialResult as AsyncExpectationResult;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only change to non-test code. The cast was unnecessary as TS could infer it from it being a promise

const asyncError = new JestAssertionError();
if (Error.captureStackTrace) {
Error.captureStackTrace(asyncError, throwingMatcher);
}

return asyncResult
return potentialResult
.then(aResult => processResult(aResult, asyncError))
.catch(handleError);
} else {
const syncResult = potentialResult as SyncExpectationResult;

return processResult(syncResult);
return processResult(potentialResult);
}
} catch (error: any) {
return handleError(error);
Expand Down
8 changes: 2 additions & 6 deletions scripts/buildTs.mjs
Expand Up @@ -14,13 +14,9 @@ import globby from 'globby';
import fs from 'graceful-fs';
import pLimit from 'p-limit';
import stripJsonComments from 'strip-json-comments';
import {getPackages} from './buildUtils.mjs';
import {getPackagesWithTsConfig} from './buildUtils.mjs';

const packages = getPackages();

const packagesWithTs = packages.filter(p =>
fs.existsSync(path.resolve(p.packageDir, 'tsconfig.json')),
);
const packagesWithTs = getPackagesWithTsConfig();

const {stdout: allWorkspacesString} = await execa('yarn', [
'workspaces',
Expand Down
6 changes: 6 additions & 0 deletions scripts/buildUtils.mjs
Expand Up @@ -121,3 +121,9 @@ export function adjustToTerminalWidth(str) {
}
return strs.slice(0, -1).concat(lastString).join('\n');
}

export function getPackagesWithTsConfig() {
return getPackages().filter(p =>
fs.existsSync(path.resolve(p.packageDir, 'tsconfig.json')),
);
}
90 changes: 90 additions & 0 deletions scripts/lintTs.mjs
@@ -0,0 +1,90 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as os from 'os';
import * as path from 'path';
import * as url from 'url';
import chalk from 'chalk';
import {ESLint} from 'eslint';
import pLimit from 'p-limit';
import {getPackagesWithTsConfig} from './buildUtils.mjs';

// we want to limit the number of processes we spawn
const cpus = Math.max(1, os.cpus().length - 1);
const mutex = pLimit(cpus);

const fix = process.argv.slice(2).some(arg => arg === '--fix');

const monorepoRoot = path.resolve(url.fileURLToPath(import.meta.url), '../..');

const packagesWithTs = getPackagesWithTsConfig()
.map(({packageDir}) => packageDir)
.concat(path.resolve(monorepoRoot, 'e2e'))
// TODO: run all all projects
SimenB marked this conversation as resolved.
Show resolved Hide resolved
.slice(0, 3);

try {
await Promise.all(
packagesWithTs.map(packageDir =>
mutex(async () => {
const eslint = new ESLint({
cache: true,
cacheLocation: path.resolve(packageDir, '.eslintcache'),
cwd: monorepoRoot,
extensions: ['.ts'],
fix,
overrideConfig: {
extends: [
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: ['./tsconfig.json', `${packageDir}/tsconfig.json`],
tsconfigRootDir: monorepoRoot,
},
plugins: ['@typescript-eslint'],
root: true,
},
});

const filesToLint = packageDir.endsWith('e2e')
? `${packageDir}/__tests__/`
: `${packageDir}/src/`;

let results = await eslint.lintFiles(filesToLint);

if (fix) {
await ESLint.outputFixes(results);

// re-run after outputting fixes
results = await eslint.lintFiles(filesToLint);
}

const filteredResults = ESLint.getErrorResults(results);

if (filteredResults.length > 0) {
const formatter = await eslint.loadFormatter('stylish');
const resultText = formatter.format(results);

console.error(resultText);

throw new Error('Got lint errors');
}
}),
),
);
} catch (e) {
console.error(
chalk.inverse.red(' Unable to lint using TypeScript info files '),
);

throw e;
}

console.log(
chalk.inverse.green(' Successfully linted using TypeScript info files '),
);