Skip to content

Commit

Permalink
Upgrade to eslint v8 (#1035)
Browse files Browse the repository at this point in the history
* WIP on updating to eslint 8

* Use latest babel parser

* Get coverage working with eslint v8

* Fix coverage for typescript

* Fix assertion path sep for windows

Co-authored-by: geek <[email protected]>
  • Loading branch information
devinivy and geek authored Apr 20, 2022
1 parent 43f0b14 commit ae18c6a
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 36 deletions.
2 changes: 1 addition & 1 deletion lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ exports.run = async function () {
settings.lintingPath = process.cwd();

if (settings.coverage) {
Modules.coverage.instrument(settings);
await Modules.coverage.instrument(settings);
}
else if (settings.transform) {
Modules.transform.install(settings);
Expand Down
1 change: 0 additions & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ for (const module of ['coverage', 'leaks', 'types']) {
Object.defineProperty(exports, module, Object.getOwnPropertyDescriptor(Modules, module));
}


/*
experiment('Utilities', () => {
Expand Down
23 changes: 13 additions & 10 deletions lib/linter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const Hoek = require('@hapi/hoek');
const internals = {};


exports.lint = function () {
exports.lint = async function () {

const configuration = {
ignore: true
Expand All @@ -24,7 +24,7 @@ exports.lint = function () {
!Fs.existsSync('.eslintrc.yml') &&
!Fs.existsSync('.eslintrc.json') &&
!Fs.existsSync('.eslintrc')) {
configuration.configFile = Path.join(__dirname, '.eslintrc.js');
configuration.overrideConfigFile = Path.join(__dirname, '.eslintrc.js');
}

if (options) {
Expand All @@ -33,17 +33,14 @@ exports.lint = function () {

let results;
try {
const engine = new Eslint.CLIEngine(configuration);
results = engine.executeOnFiles(['.']);
const eslint = new Eslint.ESLint(configuration);
results = await eslint.lintFiles(['.']);
}
catch (ex) {
results = {
results: [{ messages: [ex] }]
};
results = [{ messages: [ex] }];
}


return results.results.map((result) => {
return results.map((result) => {

const transformed = {
filename: result.filePath
Expand All @@ -68,4 +65,10 @@ exports.lint = function () {
});
};

process.send(exports.lint());
const main = async () => {

const results = await exports.lint();
process.send(results);
};

main();
31 changes: 22 additions & 9 deletions lib/modules/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Transform = require('./transform');
const internals = {
ext: Symbol.for('@hapi/lab/coverage/initialize'),
_state: Symbol.for('@hapi/lab/coverage/_state'),
EslintEngine: new ESLint.CLIEngine({ baseConfig: Eslintrc })
eslint: new ESLint.ESLint({ baseConfig: Eslintrc })
};


Expand Down Expand Up @@ -69,16 +69,18 @@ if (typeof global.__$$labCov === 'undefined') {
// $lab:coverage:on$


exports.instrument = function (options) {
exports.instrument = async function (options) {

if (options['coverage-module']) {
for (const name of options['coverage-module']) {
internals.state.modules.add(name);
}
}

const ctx = await internals.context(options);

internals.state.patterns.unshift(internals.pattern(options));
Transform.install(options, internals.prime);
Transform.install(options, (ext) => internals.prime(ext, ctx));
};


Expand Down Expand Up @@ -114,15 +116,15 @@ internals.escape = function (string) {
};


internals.prime = function (extension) {
internals.prime = function (extension, ctx) {

require.extensions[extension] = function (localModule, filename) {

// We never want to instrument eslint configs in order to avoid infinite recursion
if (Path.basename(filename, extension) !== '.eslintrc') {
for (let i = 0; i < internals.state.patterns.length; ++i) {
if (internals.state.patterns[i].test(filename.replace(/\\/g, '/'))) {
return localModule._compile(internals.instrument(filename), filename);
return localModule._compile(internals.instrument(filename, ctx), filename);
}
}
}
Expand All @@ -133,7 +135,7 @@ internals.prime = function (extension) {
};


internals.instrument = function (filename) {
internals.instrument = function (filename, ctx) {

filename = filename.replace(/\\/g, '/');

Expand Down Expand Up @@ -331,9 +333,8 @@ internals.instrument = function (filename) {

// Parse tree

const eslintConfig = internals.EslintEngine.getConfigForFile(filename);
const tree = ESLintParser.parse(content, {
...eslintConfig.parserOptions,
...ctx.parserOptions,
loc: true,
range: true,
comment: true
Expand Down Expand Up @@ -520,6 +521,7 @@ exports.analyze = async function (options) {

const report = internals.state.files;
const pattern = internals.pattern(options);
const ctx = await internals.context(options);

const cov = {
sloc: 0,
Expand All @@ -537,7 +539,7 @@ exports.analyze = async function (options) {
const filename = file.replace(/\\/g, '/');
if (pattern.test(filename)) {
if (!report[filename]) {
internals.instrument(filename);
internals.instrument(filename, ctx);
}

report[filename].source = internals.state.sources[filename] || [];
Expand Down Expand Up @@ -778,3 +780,14 @@ internals.external = function (filename) {

return reports.length ? reports : null;
};

internals.context = async (options) => {

// The parserOptions are shared by all files for coverage purposes, based on
// the effective eslint config for a hypothetical file {coveragePath}/x.js
const { parserOptions } = await internals.eslint.calculateConfigForFile(
Path.join(options.coveragePath || '', 'x.js')
);

return { parserOptions };
};
7 changes: 4 additions & 3 deletions lib/modules/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ internals.transform = function (content, fileName) {
if (!internals.configs.has(configFile)) {
try {
var { config, error } = Typescript.readConfigFile(configFile, Typescript.sys.readFile);
if (error) {
throw new Error(`TypeScript config error in ${configFile}: ${error.messageText}`);
}
}
catch (err) {
throw new Error(`Cannot find a tsconfig file for ${fileName}`);
}

if (error) {
throw new Error(`TypeScript config error in ${configFile}: ${error.messageText}`);
}

const { options } = Typescript.parseJsonConfigFileContent(config, Typescript.sys, Typescript.getDirectoryPath(configFile), {}, configFile);
options.sourceMap = false;
options.inlineSourceMap = true;
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
]
},
"dependencies": {
"@babel/core": "^7.14.3",
"@babel/eslint-parser": "^7.14.3",
"@babel/core": "^7.16.0",
"@babel/eslint-parser": "^7.16.0",
"@hapi/bossy": "5.x.x",
"@hapi/eslint-plugin": "^5.1.0",
"@hapi/hoek": "9.x.x",
"diff": "^5.0.0",
"eslint": "7.x.x",
"eslint": "8.x.x",
"find-rc": "4.x.x",
"globby": "10.x.x",
"handlebars": "4.x.x",
Expand Down
29 changes: 22 additions & 7 deletions test/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ const internals = {


const lab = exports.lab = _Lab.script();
const before = lab.before;
const describe = lab.describe;
const it = lab.it;
const expect = Code.expect;


describe('Coverage', () => {

Lab.coverage.instrument({ coveragePath: Path.join(__dirname, 'coverage'), coverageExclude: 'exclude' });
before({}, async () => {

await Lab.coverage.instrument({ coveragePath: Path.join(__dirname, 'coverage'), coverageExclude: 'exclude' });
});

it('computes sloc without comments', async () => {

Expand Down Expand Up @@ -132,7 +136,7 @@ describe('Coverage', () => {
expect(cov.percent).to.equal(100);
});

it('logs to stderr when coverageExclude file has fs.stat issue', async () => {
it('logs to stderr when coverageExclude file has fs.stat issue', async (flags) => {

const Test = require('./coverage/test-folder/test-name.js');

Expand All @@ -141,8 +145,15 @@ describe('Coverage', () => {
const origStatSync = Fs.statSync;
const origErrorLog = console.error;

let calls = 0;
Fs.statSync = () => {

calls++;
if (calls === 3) {
// Only mock for first 3 calls
Fs.statSync = origStatSync;
}

const err = new Error();
err.code = 'BOOM';
throw err;
Expand All @@ -153,9 +164,13 @@ describe('Coverage', () => {
expect(data.code).to.equal('BOOM');
};

flags.onCleanup = () => {

Fs.statSync = origStatSync;
console.error = origErrorLog;
};

const cov = await Lab.coverage.analyze({ coveragePath: Path.join(__dirname, 'coverage/test-folder'), coverageExclude: ['test', 'node_modules', 'test-name.js'] });
Fs.statSync = origStatSync;
console.error = origErrorLog;
expect(cov.percent).to.equal(100);
});

Expand Down Expand Up @@ -577,7 +592,7 @@ describe('Coverage', () => {
it('reports external coverage', async () => {

const coveragePath = Path.join(__dirname, 'coverage/module');
Lab.coverage.instrument({ coveragePath, 'coverage-module': ['@hapi/lab-external-module-test'] });
await Lab.coverage.instrument({ coveragePath, 'coverage-module': ['@hapi/lab-external-module-test'] });

require(coveragePath);

Expand Down Expand Up @@ -615,12 +630,12 @@ describe('Coverage', () => {

describe('Coverage via Transform API', () => {

lab.before(() => {
lab.before(async () => {

internals.js = require.extensions['.js'];
internals.inl = require.extensions['.inl'];

Lab.coverage.instrument({ coveragePath: Path.join(__dirname, 'coverage'), coverageExclude: 'exclude', transform: internals.transform });
await Lab.coverage.instrument({ coveragePath: Path.join(__dirname, 'coverage'), coverageExclude: 'exclude', transform: internals.transform });
});

lab.after(() => {
Expand Down
6 changes: 5 additions & 1 deletion test/reporters.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ const internals = {};


const lab = exports.lab = _Lab.script();
const before = lab.before;
const describe = lab.describe;
const it = lab.it;
const expect = Code.expect;


describe('Reporter', () => {

Lab.coverage.instrument({ coveragePath: Path.join(__dirname, './coverage/'), coverageExclude: 'exclude' });
before({}, async () => {

await Lab.coverage.instrument({ coveragePath: Path.join(__dirname, './coverage/'), coverageExclude: 'exclude' });
});

it('outputs to a stream', async () => {

Expand Down
6 changes: 5 additions & 1 deletion test/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ const internals = {
// Test shortcuts

const lab = exports.lab = _Lab.script();
const before = lab.before;
const describe = lab.describe;
const it = lab.it;
const expect = Code.expect;

describe('Transform', () => {

Lab.coverage.instrument({ coveragePath: Path.join(__dirname, './transform/'), coverageExclude: 'exclude', transform: internals.transform });
before({}, async () => {

await Lab.coverage.instrument({ coveragePath: Path.join(__dirname, './transform/'), coverageExclude: 'exclude', transform: internals.transform });
});

it('instruments and measures coverage', async () => {

Expand Down
33 changes: 33 additions & 0 deletions test/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const Path = require('path');
const Code = require('@hapi/code');
const _Lab = require('../test_runner');
const RunCli = require('./run_cli');
const Ts = require('typescript');
const Typescript = require('../lib/modules/typescript');


Expand Down Expand Up @@ -53,6 +54,31 @@ describe('TypeScript', () => {

describe('transform', () => {

it('errors when failing to find a tsconfig file', () => {

const path = Path.join(__dirname, 'cli', 'simple.js');

expect(
() => Typescript.extensions[0].transform(Fs.readFileSync(path, { encoding: 'utf8' }), path)
).to.throw(/^Cannot find a tsconfig file for .+cli[\/\\]simple\.js/);
});

it('errors when unable to read a tsconfig file', (flags) => {

const path = Path.join(__dirname, 'cli_typescript', 'simple.ts');

const origReadFile = Ts.sys.readFile;
flags.onCleanup = () => Object.assign(Ts.sys, { readFile: origReadFile });
Ts.sys.readFile = () => {

throw new Error('Oops!');
};

expect(
() => Typescript.extensions[0].transform(Fs.readFileSync(path, { encoding: 'utf8' }), path)
).to.throw(/^TypeScript config error in .+?cli_typescript\/tsconfig\.json: Cannot read file \'.+?\/cli_typescript\/tsconfig\.json\': Oops!/);
});

it('generates embedded sourcemap with sourcesContent', () => {

const smre = /\/\/\#\s*sourceMappingURL=data:application\/json[^,]+base64,(.*)\r?\n?$/;
Expand All @@ -65,5 +91,12 @@ describe('TypeScript', () => {
expect(sourcemap.sourcesContent).to.exist();
expect(sourcemap.sourcesContent).to.have.length(1);
});

it('transforms identically when called multiple times', () => {
// This covers config file caching behavior, which is not directly observable by consumers.
const path = Path.join(__dirname, 'cli_typescript', 'simple.ts');
const transform = () => Typescript.extensions[0].transform(Fs.readFileSync(path, { encoding: 'utf8' }), path);
expect(transform()).to.equal(transform());
});
});
});

0 comments on commit ae18c6a

Please sign in to comment.