Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@automattic/eslint-changed

Package Overview
Dependencies
Maintainers
37
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@automattic/eslint-changed - npm Package Compare versions

Comparing version 2.0.2 to 2.0.3

src/cli.js

347

bin/eslint-changed.js
#!/usr/bin/env node
/* eslint-env node */
/* eslint-disable no-console, no-process-exit */
import { createProgram } from '../src/cli.js';
const { ESLint } = require( 'eslint' );
const parseDiff = require( 'parse-diff' );
const fs = require( 'fs' );
const path = require( 'path' );
const chalk = require( 'chalk' );
const APP_VERSION = '2.0.2';
const { program } = require( 'commander' );
program
.usage(
'Run ESLint on files and only report new warnings/errors compared to the previous version.'
)
.option( '--diff <file>', 'A file containing a unified diff of the changes.' )
.option(
'--diff-base <dir>',
'Base directory the diff is relative to. Defaults to the current directory.',
v => path.resolve( process.cwd(), v )
)
.option(
'--eslint-orig <file>',
'A file containing the JSON output of ESLint on the unchanged files.'
)
.option(
'--eslint-new <file>',
'A file containing the JSON output of ESLint on the changed files.'
)
.option(
'--git',
"Assume git-versioned files. Set environment variables GIT and ESLINT to point to appropriate commands if they're not already in the path."
)
.option( '--git-staged', 'Compare the staged version to the HEAD version (this is the default).' )
.option( '--git-unstaged', 'Compare the working copy version to the staged (or HEAD) version.' )
.option(
'--git-base <ref>',
'Compare the HEAD version to the HEAD of a different base (e.g. branch).'
)
.option( '--debug', 'Enable debug output.' )
.option(
'--ext <list>',
'Comma-separated list of JavaScript file extensions. Ignored if files are listed.',
'.js'
)
.option(
'--in-diff-only',
'Only include messages on lines changed in the diff. This may miss things like deleting a `var` that leads to a new `no-undef` elsewhere.'
)
.option( '--format <name>', 'ESLint format to use for output.', 'stylish' )
.version( APP_VERSION );
program.parse();
const argv = program.opts();
if ( argv.diff !== undefined && argv.git ) {
console.error( 'error: options `--diff` and `--git` are mutually exclusive' );
process.exit( 1 );
try {
await createProgram().parseAsync();
} catch ( e ) {
console.error( e ); // eslint-disable-line no-console
process.exitCode = 1;
}
[
[ 'diff', 'eslint-orig' ],
[ 'diff', 'eslint-new' ],
[ 'diff-base', 'diff' ],
[ 'eslint-orig', 'diff' ],
[ 'eslint-new', 'diff' ],
[ 'git-staged', 'git' ],
[ 'git-unstaged', 'git' ],
[ 'git-base', 'git' ],
].forEach( x => {
const [ arg1, arg2 ] = x;
const prop1 = arg1.replace( /-[a-z]/g, v => v[ 1 ].toUpperCase() );
const prop2 = arg2.replace( /-[a-z]/g, v => v[ 1 ].toUpperCase() );
if ( argv[ prop1 ] !== undefined && argv[ prop2 ] === undefined ) {
console.error( `error: option \`--${ arg1 }\` requires option \`--${ arg2 }\`` );
process.exit( 1 );
}
} );
const debug = argv.debug ? ( ...m ) => console.debug( chalk.grey( ...m ) ) : () => {};
/**
* Get files from a diff.
*
* @param {Array} diff - Diff array from `parse-diff`.
* @returns {string[]} File name strings.
*/
function getFilesFromDiff( diff ) {
let files = diff.map( x => x.to );
if ( program.args.length === 0 ) {
const exts = argv.ext.split( ',' );
files = files.filter( file => exts.some( ext => file.endsWith( ext ) ) );
}
return files;
}
const spawnSync = require( 'child_process' ).spawnSync;
const spawnOpt = {
stdio: [ null, 'pipe', 'inherit' ],
maxBuffer: Infinity,
encoding: 'utf8',
};
/**
* Spawn a command, exiting if it fails.
*
* @param {string} cmd - Command to execute.
* @param {string[]} cmdArgs - Arguments to the command.
* @returns {string} Command output.
*/
function doCmd( cmd, cmdArgs ) {
const res = spawnSync( cmd, cmdArgs, spawnOpt );
if ( res.status ) {
process.exit( res.status );
}
return res.stdout;
}
/**
* Main method.
*/
async function main() {
const eslint = new ESLint();
debug( 'Using ESLint version', eslint.version );
const formatter = await eslint.loadFormatter( argv.format );
let diff, diffBase, files, eslintOrig, eslintNew;
if ( argv.git ) {
const git = process.env.GIT || 'git';
let origRef, newRef, args, ret;
ret = spawnSync( git, [ '--version' ], spawnOpt );
if ( ret.error ) {
console.error(
`error: failed to execute git as \`${ git }\`. Use environment variable \`GIT\` to override.`
);
process.exit( 1 );
}
debug( 'Using git version', ret.stdout.trim() );
args = [ 'rev-parse', '--show-toplevel' ];
debug( 'Getting git top level:', git, args.join( ' ' ) );
diffBase = doCmd( git, args ).trim();
args = [ 'diff' ];
if ( argv.gitBase !== undefined ) {
const args2 = [ 'merge-base', argv.gitBase, 'HEAD' ];
debug( 'Running git merge-base command:', git, args2.join( ' ' ) );
origRef = doCmd( git, args2 ).trim();
debug( 'Merge base is:', origRef );
newRef = 'HEAD';
args.push( `${ origRef }...HEAD` );
} else if ( argv.gitUnstaged ) {
origRef = ':0';
newRef = null;
} else {
origRef = 'HEAD';
newRef = ':0';
args.push( '--staged' );
}
args = args.concat( program.args );
debug( 'Running git diff command:', git, args.join( ' ' ) );
diff = parseDiff( doCmd( git, args ) );
if ( ! argv.inDiffOnly && program.args.length ) {
files = program.args;
debug( 'Determined files from command line:', files );
} else {
files = getFilesFromDiff( diff );
debug( 'Determined files from diff:', files );
}
eslintOrig = [];
eslintNew = [];
for ( const file of files ) {
let content;
args = [ 'cat-file', '-e', origRef + ':' + file ];
debug( 'Testing if file is new:', git, args.join( ' ' ) );
if ( spawnSync( git, args, { stdio: 'ignore' } ).status ) {
debug( "It's new, so no orig ESLint data." );
} else {
args = [ 'show', origRef + ':' + file ];
debug( 'Fetching orig file contents:', git, args.join( ' ' ) );
content = doCmd( git, args );
debug( 'Executing ESLint for orig file' );
ret = await eslint.lintText( content, {
filePath: file,
} );
eslintOrig = eslintOrig.concat( ret );
}
if ( newRef === null ) {
content = fs.readFileSync( file, 'utf8' );
} else {
args = [ 'show', newRef + ':' + file ];
debug( 'Fetching new file contents:', git, args.join( ' ' ) );
content = doCmd( git, args );
}
debug( 'Executing ESLint for new file' );
ret = await eslint.lintText( content, {
filePath: file,
} );
eslintNew = eslintNew.concat( ret );
}
} else if ( argv.diff ) {
diff = parseDiff( fs.readFileSync( argv.diff, 'utf8' ) );
diffBase = argv.diffBase || process.cwd();
if ( ! argv.inDiffOnly && program.args.length ) {
files = program.args;
debug( 'Determined files from command line:', files );
} else {
files = getFilesFromDiff( diff );
debug( 'Determined files from diff:', files );
if ( program.args.length ) {
const cmdLineFiles = new Set( program.args );
files = files.filter( file => cmdLineFiles.has( file ) );
debug( 'Intersected files with those from the command line:', files );
}
}
eslintOrig = JSON.parse( fs.readFileSync( argv.eslintOrig, 'utf8' ) );
eslintNew = JSON.parse( fs.readFileSync( argv.eslintNew, 'utf8' ) );
} else {
program.outputHelp();
process.exit( 1 );
}
// oldLines maps line numbers in the old version to the new.
// newLines just lists lines present in the diff.
const oldLines = {};
const newLines = {};
diff.forEach( file => {
const fileName = path.resolve( diffBase, file.to );
const ol = {},
nl = {};
file.chunks.forEach( chunk => {
let p = 0;
chunk.changes.forEach( c => {
switch ( c.type ) {
case 'add':
p = c.ln;
nl[ c.ln ] = true;
break;
case 'del':
ol[ c.ln ] = ++p;
break;
case 'normal':
p = c.ln2;
ol[ c.ln1 ] = c.ln2;
nl[ c.ln2 ] = true;
break;
}
} );
} );
oldLines[ fileName ] = ol;
newLines[ fileName ] = nl;
} );
if ( argv.inDiffOnly ) {
files = new Set( files.map( file => path.resolve( diffBase, file ) ) );
eslintOrig = eslintOrig.filter( x => files.has( x.filePath ) && oldLines[ x.filePath ] );
eslintNew = eslintNew.filter( x => files.has( x.filePath ) && newLines[ x.filePath ] );
}
const origMsgs = {};
eslintOrig.forEach( file => {
const lines = {};
const oldL = oldLines[ file.filePath ] || {};
file.messages.forEach( msg => {
let line = msg.line;
for ( let i = msg.line; i > 0; i-- ) {
if ( oldL[ i ] ) {
line = msg.line + oldL[ i ] - i;
break;
}
}
debug( `Orig ${ file.filePath }: Found ${ msg.ruleId } on line ${ msg.line } => ${ line }` );
if ( ! lines[ line ] ) {
lines[ line ] = {};
}
lines[ line ][ msg.ruleId ] = msg.line;
} );
origMsgs[ file.filePath ] = lines;
} );
let exitCode = 0;
eslintNew.forEach( file => {
const newL = newLines[ file.filePath ] || {};
const messages = file.messages;
file.messages = [];
file.errorCount = 0;
file.fatalErrorCount = 0;
file.warningCount = 0;
file.fixableErrorCount = 0;
file.fixableWarningCount = 0;
messages.forEach( msg => {
if ( argv.inDiffOnly && ! newL[ msg.line ] ) {
debug(
`New ${ file.filePath }: Ignoring ${ msg.ruleId } on line ${ msg.line }, not in diff`
);
return;
}
const l =
origMsgs[ file.filePath ] &&
origMsgs[ file.filePath ][ msg.line ] &&
origMsgs[ file.filePath ][ msg.line ][ msg.ruleId ];
if ( l ) {
debug(
`New ${ file.filePath }: Ignoring ${ msg.ruleId } on line ${ msg.line }, found in orig at line ${ l }`
);
return;
}
debug( `New ${ file.filePath }: Found ${ msg.ruleId } on line ${ msg.line }` );
file.messages.push( msg );
if ( msg.severity === 1 ) {
file.warningCount++;
file.fixableWarningCount += msg.fix ? 1 : 0;
} else {
file.errorCount++;
file.fatalErrorCount += msg.fatal ? 1 : 0;
file.fixableErrorCount += msg.fix ? 1 : 0;
}
exitCode = 1;
} );
} );
console.log( formatter.format( eslintNew ) );
process.exit( exitCode );
}
main().catch( e => {
console.error( e );
process.exit( 1 );
} );

@@ -8,2 +8,15 @@ # Changelog

## [2.0.3] - 2022-07-06
### Changed
- Convert code to ESM. [#24626]
- Remove use of `pnpx` in preparation for pnpm 7.0. [#24210]
- Renaming `master` references to `trunk`. [#24712, #24661]
- Reorder JS imports for `import/order` eslint rule. [#24601]
- Updated package dependencies. [#24045]
- Use `jest` for tests. [#24626]
### Fixed
- Fix debug output. [#24819]
- Fix interaction of `--in-diff-only` (or lack thereof) and listing of files on the command line. [#24626]
## [2.0.2] - 2022-04-18

@@ -50,2 +63,3 @@ ### Changed

[2.0.3]: https://github.com/Automattic/eslint-changed/compare/2.0.2...2.0.3
[2.0.2]: https://github.com/Automattic/eslint-changed/compare/2.0.1...2.0.2

@@ -52,0 +66,0 @@ [2.0.1]: https://github.com/Automattic/eslint-changed/compare/2.0.0...2.0.1

{
"name": "@automattic/eslint-changed",
"version": "2.0.2",
"version": "2.0.3",
"description": "Run eslint on files, but only report warnings and errors from lines that were changed.",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/eslint-changed/#readme",
"type": "module",
"bugs": {

@@ -20,11 +21,12 @@ "url": "https://github.com/Automattic/jetpack/labels/[JS Package] Eslint Changed"

"scripts": {
"test": "js-test-runner 'glob:tests/**/*.js'"
"test": "NODE_OPTIONS=--experimental-vm-modules jest --config=tests/jest.config.cjs",
"test-coverage": "NODE_OPTIONS=--experimental-vm-modules jest --config=tests/jest.config.cjs --coverage --coverageDirectory=\"$COVERAGE_DIR\" --coverageReporters=clover"
},
"dependencies": {
"chalk": "4.1.2",
"commander": "7.2.0",
"chalk": "5.0.1",
"commander": "9.3.0",
"parse-diff": "0.8.1"
},
"devDependencies": {
"nyc": "15.1.0"
"jest": "28.1.0"
},

@@ -31,0 +33,0 @@ "peerDependencies": {

@@ -54,5 +54,5 @@ # ESLint Changed

This will compare HEAD with origin/master.
This will compare HEAD with origin/trunk.
```bash
npx eslint-changed --git --git-base origin/master
npx eslint-changed --git --git-base origin/trunk
```

@@ -63,6 +63,6 @@

# Produce a diff.
git diff origin/master...HEAD > /tmp/diff
git diff origin/trunk...HEAD > /tmp/diff
# Check out the merge-base of origin/master and HEAD.
git checkout origin/master...HEAD
# Check out the merge-base of origin/trunk and HEAD.
git checkout origin/trunk...HEAD

@@ -69,0 +69,0 @@ # Run ESLint.

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc