blob: 7bda43feb3650e3988c2d07d0164db8829b754df [file] [log] [blame]
// Copyright 2024 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Use V8's code cache to speed up instantiation time.
require('v8-compile-cache');
const {ESLint} = require('eslint');
const stylelint = require('stylelint');
const {extname, join} = require('path');
const globby = require('globby');
const yargs = require('yargs/yargs');
const {hideBin} = require('yargs/helpers');
const childProcess = require('child_process');
const {
devtoolsRootPath,
litAnalyzerExecutablePath,
nodePath,
} = require('../devtools_paths.js');
const flags = yargs(hideBin(process.argv))
.option('fix', {
type: 'boolean',
default: true,
describe: 'Automatically fix, where possible, problems reported by rules.',
})
.usage(
'$0 [<files...>]', 'Run the linter on the provided files',
yargs => {
yargs.positional('files', {
describe: 'File(s), glob(s), or directories',
type: 'array',
default: [
'front_end',
'inspector_overlay',
'scripts',
'test',
],
});
})
.parse();
if (!flags.fix) {
console.log('[lint]: fix is disabled; no errors will be autofixed.');
}
async function runESLint(files) {
const cli = new ESLint({
ignorePath: join(__dirname, '..', '..', '.eslintignore'),
fix: flags.fix,
});
// We filter out certain files in the `.eslintignore`. However, ESLint produces warnings
// when you include a particular file that is ignored. This means that if you edit a file
// that is directly ignored in the `.eslintignore`, ESLint would report a failure.
// This was originally reported in https://github.com/eslint/eslint/issues/9977
// The suggested workaround is to use the CLIEngine to pre-emptively filter out these
// problematic paths.
files = await Promise.all(files.map(async file => (await cli.isPathIgnored(file)) ? null : file));
files = files.filter(file => file !== null);
const results = await cli.lintFiles(files);
if (flags.fix) {
await ESLint.outputFixes(results);
}
const formatter = await cli.loadFormatter('stylish');
const output = formatter.format(results);
if (output) {
console.log(output);
}
return !results.find(report => report.errorCount + report.warningCount > 0);
}
async function runStylelint(files) {
const {output, errored} = await stylelint.lint({
configFile: join(__dirname, '..', '..', '.stylelintrc.json'),
ignorePath: join(__dirname, '..', '..', '.stylelintignore'),
fix: flags.fix,
files,
formatter: 'string',
});
if (output) {
console.log(output);
}
return !errored;
}
function runLitAnalyzer(files) {
const rules = {'no-missing-import': 'error', 'no-unknown-tag-name': 'error', 'no-complex-attribute-binding': 'off'};
const args =
[litAnalyzerExecutablePath(), ...Object.entries(rules).flatMap(([k, v]) => [`--rules.${k}`, v]), ...files];
const result =
childProcess.spawnSync(nodePath(), args, {encoding: 'utf-8', cwd: devtoolsRootPath(), stdio: 'inherit'});
return result.status === 0;
}
async function run() {
const scripts = [];
const styles = [];
for (const path of globby.sync(flags.files, {expandDirectories: {extensions: ['css', 'js', 'ts']}})) {
if (extname(path) === '.css') {
styles.push(path);
} else {
scripts.push(path);
}
}
let succeed = true;
if (scripts.length !== 0) {
succeed &&= await runESLint(scripts);
succeed &&= runLitAnalyzer(scripts);
}
if (styles.length !== 0) {
succeed &&= await runStylelint(styles);
}
return succeed;
}
run()
.then(succeed => {
process.exit(succeed ? 0 : 1);
})
.catch(err => {
console.log(`[lint]: ${err.message}`, err.stack);
process.exit(1);
});