2018-09-17 21:09:02 +00:00
#!/usr/bin/env node
2023-06-22 14:21:42 +00:00
const crypto = require ( 'node:crypto' ) ;
2020-03-20 20:28:31 +00:00
const { GitProcess } = require ( 'dugite' ) ;
2023-06-22 14:21:42 +00:00
const childProcess = require ( 'node:child_process' ) ;
2020-12-01 01:47:29 +00:00
const { ESLint } = require ( 'eslint' ) ;
2023-06-22 14:21:42 +00:00
const fs = require ( 'node:fs' ) ;
2020-03-20 20:28:31 +00:00
const minimist = require ( 'minimist' ) ;
2023-06-22 14:21:42 +00:00
const path = require ( 'node:path' ) ;
2023-11-21 07:50:08 +00:00
const { getCodeBlocks } = require ( '@electron/lint-roller/dist/lib/markdown' ) ;
2018-09-17 21:09:02 +00:00
2023-01-24 08:00:25 +00:00
const { chunkFilenames , findMatchingFiles } = require ( './lib/utils' ) ;
2022-06-22 10:23:11 +00:00
2021-11-22 07:34:31 +00:00
const ELECTRON _ROOT = path . normalize ( path . dirname ( _ _dirname ) ) ;
const SOURCE _ROOT = path . resolve ( ELECTRON _ROOT , '..' ) ;
const DEPOT _TOOLS = path . resolve ( SOURCE _ROOT , 'third_party' , 'depot_tools' ) ;
2018-09-17 21:09:02 +00:00
2022-07-11 10:25:17 +00:00
// Augment the PATH for this script so that we can find executables
// in the depot_tools folder even if folks do not have an instance of
// DEPOT_TOOLS in their path already
process . env . PATH = ` ${ process . env . PATH } ${ path . delimiter } ${ DEPOT _TOOLS } ` ;
2020-06-09 18:29:29 +00:00
const IGNORELIST = new Set ( [
2019-06-19 20:56:58 +00:00
[ 'shell' , 'browser' , 'resources' , 'win' , 'resource.h' ] ,
[ 'shell' , 'common' , 'node_includes.h' ] ,
2023-02-17 18:56:09 +00:00
[ 'spec' , 'fixtures' , 'pages' , 'jquery-3.6.0.min.js' ]
2021-11-22 07:34:31 +00:00
] . map ( tokens => path . join ( ELECTRON _ROOT , ... tokens ) ) ) ;
2018-09-17 21:09:02 +00:00
2020-10-19 19:08:13 +00:00
const IS _WINDOWS = process . platform === 'win32' ;
2023-06-08 14:25:56 +00:00
const CPPLINT _FILTERS = [
// from presubmit_canned_checks.py OFF_BY_DEFAULT_LINT_FILTERS
'-build/include' ,
'-build/include_order' ,
'-build/namespaces' ,
'-readability/casting' ,
'-runtime/int' ,
'-whitespace/braces' ,
// from presubmit_canned_checks.py OFF_UNLESS_MANUALLY_ENABLED_LINT_FILTERS
'-build/c++11' ,
'-build/header_guard' ,
'-readability/todo' ,
'-runtime/references' ,
'-whitespace/braces' ,
'-whitespace/comma' ,
'-whitespace/end_of_line' ,
'-whitespace/forcolon' ,
'-whitespace/indent' ,
'-whitespace/line_length' ,
'-whitespace/newline' ,
'-whitespace/operators' ,
'-whitespace/parens' ,
'-whitespace/semicolon' ,
'-whitespace/tab'
] ;
2018-09-17 21:09:02 +00:00
function spawnAndCheckExitCode ( cmd , args , opts ) {
2022-06-27 08:29:18 +00:00
opts = { stdio : 'inherit' , ... opts } ;
2021-09-29 17:10:13 +00:00
const { error , status , signal } = childProcess . spawnSync ( cmd , args , opts ) ;
if ( error ) {
2022-07-05 15:49:56 +00:00
// the subprocess failed or timed out
2021-09-29 17:10:13 +00:00
console . error ( error ) ;
process . exit ( 1 ) ;
}
if ( status === null ) {
// the subprocess terminated due to a signal
console . error ( signal ) ;
process . exit ( 1 ) ;
}
if ( status !== 0 ) {
// `status` is an exit code
process . exit ( status ) ;
}
2018-09-17 21:09:02 +00:00
}
2019-05-02 12:05:37 +00:00
function cpplint ( args ) {
2022-06-08 08:29:39 +00:00
args . unshift ( ` --root= ${ SOURCE _ROOT } ` ) ;
2024-04-19 13:27:58 +00:00
const cmd = IS _WINDOWS ? 'cpplint.bat' : 'cpplint.py' ;
const result = childProcess . spawnSync ( cmd , args , { encoding : 'utf8' , shell : true } ) ;
2019-05-02 12:05:37 +00:00
// cpplint.py writes EVERYTHING to stderr, including status messages
if ( result . stderr ) {
for ( const line of result . stderr . split ( /[\r\n]+/ ) ) {
if ( line . length && ! line . startsWith ( 'Done processing ' ) && line !== 'Total errors found: 0' ) {
2020-03-20 20:28:31 +00:00
console . warn ( line ) ;
2019-05-02 12:05:37 +00:00
}
}
}
2020-10-19 19:08:13 +00:00
if ( result . status !== 0 ) {
if ( result . error ) console . error ( result . error ) ;
process . exit ( result . status || 1 ) ;
2019-05-02 12:05:37 +00:00
}
}
2020-02-04 20:19:40 +00:00
function isObjCHeader ( filename ) {
2020-03-20 20:28:31 +00:00
return /\/(mac|cocoa)\// . test ( filename ) ;
2020-02-04 20:19:40 +00:00
}
2020-03-20 15:12:18 +00:00
const LINTERS = [ {
2023-11-27 00:26:33 +00:00
key : 'cpp' ,
2019-12-05 09:46:34 +00:00
roots : [ 'shell' ] ,
2020-02-04 20:19:40 +00:00
test : filename => filename . endsWith ( '.cc' ) || ( filename . endsWith ( '.h' ) && ! isObjCHeader ( filename ) ) ,
2018-09-17 21:09:02 +00:00
run : ( opts , filenames ) => {
2022-06-22 10:23:11 +00:00
const clangFormatFlags = opts . fix ? [ '--fix' ] : [ ] ;
for ( const chunk of chunkFilenames ( filenames ) ) {
spawnAndCheckExitCode ( 'python3' , [ 'script/run-clang-format.py' , ... clangFormatFlags , ... chunk ] ) ;
2023-06-08 14:25:56 +00:00
cpplint ( [ ` --filter= ${ CPPLINT _FILTERS . join ( ',' ) } ` , ... chunk ] ) ;
2018-10-16 05:59:45 +00:00
}
2019-05-02 12:05:37 +00:00
}
} , {
key : 'objc' ,
2019-06-19 20:56:58 +00:00
roots : [ 'shell' ] ,
2021-11-22 00:36:32 +00:00
test : filename => filename . endsWith ( '.mm' ) || ( filename . endsWith ( '.h' ) && isObjCHeader ( filename ) ) ,
2019-05-02 12:05:37 +00:00
run : ( opts , filenames ) => {
2023-01-31 13:33:50 +00:00
const clangFormatFlags = opts . fix ? [ '--fix' ] : [ ] ;
spawnAndCheckExitCode ( 'python3' , [ 'script/run-clang-format.py' , '-r' , ... clangFormatFlags , ... filenames ] ) ;
2023-06-08 14:25:56 +00:00
const filter = [ ... CPPLINT _FILTERS , '-readability/braces' ] ;
2021-11-22 00:36:32 +00:00
cpplint ( [ '--extensions=mm,h' , ` --filter= ${ filter . join ( ',' ) } ` , ... filenames ] ) ;
2018-09-17 21:09:02 +00:00
}
} , {
key : 'python' ,
roots : [ 'script' ] ,
test : filename => filename . endsWith ( '.py' ) ,
run : ( opts , filenames ) => {
2024-03-21 13:48:23 +00:00
const rcfile = path . join ( DEPOT _TOOLS , 'pylintrc-2.17' ) ;
2020-03-20 20:28:31 +00:00
const args = [ '--rcfile=' + rcfile , ... filenames ] ;
2022-06-27 08:29:18 +00:00
const env = { PYTHONPATH : path . join ( ELECTRON _ROOT , 'script' ) , ... process . env } ;
2024-03-21 13:48:23 +00:00
spawnAndCheckExitCode ( 'pylint-2.17' , args , { env } ) ;
2018-09-17 21:09:02 +00:00
}
} , {
key : 'javascript' ,
2022-08-16 19:23:13 +00:00
roots : [ 'build' , 'default_app' , 'lib' , 'npm' , 'script' , 'spec' ] ,
ignoreRoots : [ 'spec/node_modules' ] ,
2019-02-06 18:27:20 +00:00
test : filename => filename . endsWith ( '.js' ) || filename . endsWith ( '.ts' ) ,
2020-12-01 01:47:29 +00:00
run : async ( opts , filenames ) => {
const eslint = new ESLint ( {
2020-12-10 18:57:06 +00:00
// Do not use the lint cache on CI builds
cache : ! process . env . CI ,
cacheLocation : ` node_modules/.eslintcache. ${ crypto . createHash ( 'md5' ) . update ( fs . readFileSync ( _ _filename ) ) . digest ( 'hex' ) } ` ,
2020-12-01 01:47:29 +00:00
extensions : [ '.js' , '.ts' ] ,
2023-11-27 00:26:33 +00:00
fix : opts . fix ,
overrideConfigFile : path . join ( ELECTRON _ROOT , '.eslintrc.json' ) ,
resolvePluginsRelativeTo : ELECTRON _ROOT
2020-12-01 01:47:29 +00:00
} ) ;
const formatter = await eslint . loadFormatter ( ) ;
let successCount = 0 ;
2020-12-10 18:57:06 +00:00
const results = await eslint . lintFiles ( filenames ) ;
for ( const result of results ) {
2020-12-01 01:47:29 +00:00
successCount += result . errorCount === 0 ? 1 : 0 ;
2020-12-10 18:57:06 +00:00
if ( opts . verbose && result . errorCount === 0 && result . warningCount === 0 ) {
2020-12-01 01:47:29 +00:00
console . log ( ` ${ result . filePath } : no errors or warnings ` ) ;
2020-10-21 22:44:38 +00:00
}
2020-12-01 01:47:29 +00:00
}
2020-12-10 18:57:06 +00:00
console . log ( formatter . format ( results ) ) ;
if ( opts . fix ) {
await ESLint . outputFixes ( results ) ;
}
2020-12-01 01:47:29 +00:00
if ( successCount !== filenames . length ) {
console . error ( 'Linting had errors' ) ;
2020-10-21 22:44:38 +00:00
process . exit ( 1 ) ;
}
2018-09-17 21:09:02 +00:00
}
2018-10-03 23:03:26 +00:00
} , {
key : 'gn' ,
roots : [ '.' ] ,
test : filename => filename . endsWith ( '.gn' ) || filename . endsWith ( '.gni' ) ,
run : ( opts , filenames ) => {
const allOk = filenames . map ( filename => {
2022-06-27 08:29:18 +00:00
const env = {
2021-11-22 07:34:31 +00:00
CHROMIUM _BUILDTOOLS _PATH : path . resolve ( ELECTRON _ROOT , '..' , 'buildtools' ) ,
2022-06-27 08:29:18 +00:00
DEPOT _TOOLS _WIN _TOOLCHAIN : '0' ,
... process . env
} ;
2018-10-24 18:25:13 +00:00
// Users may not have depot_tools in PATH.
2020-03-20 20:28:31 +00:00
env . PATH = ` ${ env . PATH } ${ path . delimiter } ${ DEPOT _TOOLS } ` ;
const args = [ 'format' , filename ] ;
if ( ! opts . fix ) args . push ( '--dry-run' ) ;
const result = childProcess . spawnSync ( 'gn' , args , { env , stdio : 'inherit' , shell : true } ) ;
2018-10-03 23:03:26 +00:00
if ( result . status === 0 ) {
2020-03-20 20:28:31 +00:00
return true ;
2018-10-03 23:03:26 +00:00
} else if ( result . status === 2 ) {
2020-03-20 20:28:31 +00:00
console . log ( ` GN format errors in " ${ filename } ". Run 'gn format " ${ filename } "' or rerun with --fix to fix them. ` ) ;
return false ;
2018-10-03 23:03:26 +00:00
} else {
2020-03-20 20:28:31 +00:00
console . log ( ` Error running 'gn format --dry-run " ${ filename } "': exit code ${ result . status } ` ) ;
return false ;
2018-10-03 23:03:26 +00:00
}
2020-03-20 20:28:31 +00:00
} ) . every ( x => x ) ;
2018-10-03 23:03:26 +00:00
if ( ! allOk ) {
2020-03-20 20:28:31 +00:00
process . exit ( 1 ) ;
2018-10-03 23:03:26 +00:00
}
}
2019-06-19 17:48:15 +00:00
} , {
key : 'patches' ,
roots : [ 'patches' ] ,
2020-11-30 07:49:01 +00:00
test : filename => filename . endsWith ( '.patch' ) ,
2019-11-04 19:04:18 +00:00
run : ( opts , filenames ) => {
2020-03-20 20:28:31 +00:00
const patchesDir = path . resolve ( _ _dirname , '../patches' ) ;
2020-11-30 07:49:01 +00:00
const patchesConfig = path . resolve ( patchesDir , 'config.json' ) ;
2022-06-16 07:46:11 +00:00
// If the config does not exist, that's a problem
2020-11-30 07:49:01 +00:00
if ( ! fs . existsSync ( patchesConfig ) ) {
2022-10-05 17:34:53 +00:00
console . error ( ` Patches config file: " ${ patchesConfig } " does not exist ` ) ;
2020-11-30 07:49:01 +00:00
process . exit ( 1 ) ;
}
2019-06-19 17:48:15 +00:00
2024-02-08 18:47:59 +00:00
for ( const target of JSON . parse ( fs . readFileSync ( patchesConfig , 'utf8' ) ) ) {
2020-11-30 07:49:01 +00:00
// The directory the config points to should exist
2024-02-08 18:47:59 +00:00
const targetPatchesDir = path . resolve ( _ _dirname , '../../..' , target . patch _dir ) ;
2022-10-05 17:34:53 +00:00
if ( ! fs . existsSync ( targetPatchesDir ) ) {
console . error ( ` target patch directory: " ${ targetPatchesDir } " does not exist ` ) ;
process . exit ( 1 ) ;
}
2020-11-30 07:49:01 +00:00
// We need a .patches file
const dotPatchesPath = path . resolve ( targetPatchesDir , '.patches' ) ;
2022-10-05 17:34:53 +00:00
if ( ! fs . existsSync ( dotPatchesPath ) ) {
console . error ( ` .patches file: " ${ dotPatchesPath } " does not exist ` ) ;
process . exit ( 1 ) ;
}
2019-06-19 17:48:15 +00:00
2020-11-30 07:49:01 +00:00
// Read the patch list
const patchFileList = fs . readFileSync ( dotPatchesPath , 'utf8' ) . trim ( ) . split ( '\n' ) ;
const patchFileSet = new Set ( patchFileList ) ;
patchFileList . reduce ( ( seen , file ) => {
if ( seen . has ( file ) ) {
2022-10-05 17:34:53 +00:00
console . error ( ` ' ${ file } ' is listed in ${ dotPatchesPath } more than once ` ) ;
process . exit ( 1 ) ;
2019-06-19 17:48:15 +00:00
}
2020-11-30 07:49:01 +00:00
return seen . add ( file ) ;
} , new Set ( ) ) ;
2022-10-05 17:34:53 +00:00
if ( patchFileList . length !== patchFileSet . size ) {
console . error ( 'Each patch file should only be in the .patches file once' ) ;
process . exit ( 1 ) ;
}
2020-11-30 07:49:01 +00:00
for ( const file of fs . readdirSync ( targetPatchesDir ) ) {
// Ignore the .patches file and READMEs
if ( file === '.patches' || file === 'README.md' ) continue ;
2019-06-19 17:48:15 +00:00
2020-11-30 07:49:01 +00:00
if ( ! patchFileSet . has ( file ) ) {
2022-10-05 17:34:53 +00:00
console . error ( ` Expected the .patches file at " ${ dotPatchesPath } " to contain a patch file (" ${ file } ") present in the directory but it did not ` ) ;
process . exit ( 1 ) ;
2019-06-19 17:48:15 +00:00
}
2020-11-30 07:49:01 +00:00
patchFileSet . delete ( file ) ;
}
// If anything is left in this set, it means it did not exist on disk
if ( patchFileSet . size > 0 ) {
2022-10-05 17:34:53 +00:00
console . error ( ` Expected all the patch files listed in the .patches file at " ${ dotPatchesPath } " to exist but some did not: \n ${ JSON . stringify ( [ ... patchFileSet . values ( ) ] , null , 2 ) } ` ) ;
process . exit ( 1 ) ;
2019-06-19 17:48:15 +00:00
}
}
2019-11-04 19:04:18 +00:00
2020-11-30 07:49:01 +00:00
const allOk = filenames . length > 0 && filenames . map ( f => {
2020-03-20 20:28:31 +00:00
const patchText = fs . readFileSync ( f , 'utf8' ) ;
2020-11-30 07:49:01 +00:00
const subjectAndDescription = / S u b j e c t : ( . * ? ) \ n \ n ( [ \ s \ S ] * ? ) \ s * ( ? = d i f f ) / m s . e x e c ( p a t c h T e x t ) ;
if ( ! subjectAndDescription [ 2 ] ) {
2020-03-20 20:28:31 +00:00
console . warn ( ` Patch file ' ${ f } ' has no description. Every patch must contain a justification for why the patch exists and the plan for its removal. ` ) ;
2020-11-30 07:49:01 +00:00
return false ;
2019-11-04 19:04:18 +00:00
}
2021-04-15 17:43:35 +00:00
const trailingWhitespaceLines = patchText . split ( /\r?\n/ ) . map ( ( line , index ) => [ line , index ] ) . filter ( ( [ line ] ) => line . startsWith ( '+' ) && /\s+$/ . test ( line ) ) . map ( ( [ , lineNumber ] ) => lineNumber + 1 ) ;
if ( trailingWhitespaceLines . length > 0 ) {
console . warn ( ` Patch file ' ${ f } ' has trailing whitespace on some lines ( ${ trailingWhitespaceLines . join ( ',' ) } ). ` ) ;
2020-11-30 07:49:01 +00:00
return false ;
2020-10-20 01:40:58 +00:00
}
2020-11-30 07:49:01 +00:00
return true ;
} ) . every ( x => x ) ;
if ( ! allOk ) {
2020-03-20 20:28:31 +00:00
process . exit ( 1 ) ;
2019-11-04 19:04:18 +00:00
}
2019-06-19 17:48:15 +00:00
}
2023-11-21 07:50:08 +00:00
} , {
key : 'md' ,
roots : [ '.' ] ,
ignoreRoots : [ 'node_modules' , 'spec/node_modules' ] ,
test : filename => filename . endsWith ( '.md' ) ,
run : async ( opts , filenames ) => {
let errors = false ;
// Run markdownlint on all Markdown files
for ( const chunk of chunkFilenames ( filenames ) ) {
2024-05-15 18:44:46 +00:00
spawnAndCheckExitCode ( 'markdownlint-cli2' , chunk ) ;
2023-11-21 07:50:08 +00:00
}
// Run the remaining checks only in docs
const docs = filenames . filter ( filename => path . dirname ( filename ) . split ( path . sep ) [ 0 ] === 'docs' ) ;
for ( const filename of docs ) {
const contents = fs . readFileSync ( filename , 'utf8' ) ;
const codeBlocks = await getCodeBlocks ( contents ) ;
for ( const codeBlock of codeBlocks ) {
const line = codeBlock . position . start . line ;
if ( codeBlock . lang ) {
// Enforce all lowercase language identifiers
if ( codeBlock . lang . toLowerCase ( ) !== codeBlock . lang ) {
console . log ( ` ${ filename } : ${ line } Code block language identifiers should be all lowercase ` ) ;
errors = true ;
}
// Prefer js/ts to javascript/typescript as the language identifier
if ( codeBlock . lang === 'javascript' ) {
console . log ( ` ${ filename } : ${ line } Use 'js' as code block language identifier instead of 'javascript' ` ) ;
errors = true ;
}
if ( codeBlock . lang === 'typescript' ) {
console . log ( ` ${ filename } : ${ line } Use 'typescript' as code block language identifier instead of 'ts' ` ) ;
errors = true ;
}
// Enforce latest fiddle code block syntax
if ( codeBlock . lang === 'javascript' && codeBlock . meta && codeBlock . meta . includes ( 'fiddle=' ) ) {
console . log ( ` ${ filename } : ${ line } Use 'fiddle' as code block language identifier instead of 'javascript fiddle=' ` ) ;
errors = true ;
}
// Ensure non-empty content in fiddle code blocks matches the file content
if ( codeBlock . lang === 'fiddle' && codeBlock . value . trim ( ) !== '' ) {
// This is copied and adapted from the website repo:
// https://github.com/electron/website/blob/62a55ca0dd14f97339e1a361b5418d2f11c34a75/src/transformers/fiddle-embedder.ts#L89C6-L101
const parseFiddleEmbedOptions = (
optStrings
) => {
// If there are optional parameters, parse them out to pass to the getFiddleAST method.
return optStrings . reduce ( ( opts , option ) => {
// Use indexOf to support bizarre combinations like `|key=Myvalue=2` (which will properly
// parse to {'key': 'Myvalue=2'})
const firstEqual = option . indexOf ( '=' ) ;
const key = option . slice ( 0 , firstEqual ) ;
const value = option . slice ( firstEqual + 1 ) ;
return { ... opts , [ key ] : value } ;
} , { } ) ;
} ;
const [ dir , ... others ] = codeBlock . meta . split ( '|' ) ;
const options = parseFiddleEmbedOptions ( others ) ;
const fiddleFilename = path . join ( dir , options . focus || 'main.js' ) ;
try {
const fiddleContent = fs . readFileSync ( fiddleFilename , 'utf8' ) . trim ( ) ;
if ( fiddleContent !== codeBlock . value . trim ( ) ) {
console . log ( ` ${ filename } : ${ line } Content for fiddle code block differs from content in ${ fiddleFilename } ` ) ;
errors = true ;
}
} catch ( err ) {
console . error ( ` ${ filename } : ${ line } Error linting fiddle code block content ` ) ;
if ( err . stack ) {
console . error ( err . stack ) ;
}
errors = true ;
}
}
}
}
}
if ( errors ) {
process . exit ( 1 ) ;
}
}
2020-03-20 20:28:31 +00:00
} ] ;
2018-09-17 21:09:02 +00:00
function parseCommandLine ( ) {
2020-03-20 20:28:31 +00:00
let help ;
2023-11-27 00:26:33 +00:00
const langs = [ 'cpp' , 'objc' , 'javascript' , 'python' , 'gn' , 'patches' , 'markdown' ] ;
const langRoots = langs . map ( lang => lang + '-roots' ) ;
const langIgnoreRoots = langs . map ( lang => lang + '-ignore-roots' ) ;
2018-09-17 21:09:02 +00:00
const opts = minimist ( process . argv . slice ( 2 ) , {
2023-11-27 00:26:33 +00:00
boolean : [ ... langs , 'help' , 'changed' , 'fix' , 'verbose' , 'only' ] ,
alias : { cpp : [ 'c++' , 'cc' , 'cxx' ] , javascript : [ 'js' , 'es' ] , python : 'py' , markdown : 'md' , changed : 'c' , help : 'h' , verbose : 'v' } ,
string : [ ... langRoots , ... langIgnoreRoots ] ,
2023-06-26 09:51:54 +00:00
unknown : ( ) => { help = true ; }
2020-03-20 20:28:31 +00:00
} ) ;
2018-09-17 21:09:02 +00:00
if ( help || opts . help ) {
2023-11-27 00:26:33 +00:00
const langFlags = langs . map ( lang => ` [-- ${ lang } ] ` ) . join ( ' ' ) ;
console . log ( ` Usage: script/lint.js ${ langFlags } [-c|--changed] [-h|--help] [-v|--verbose] [--fix] [--only -- file1 file2] ` ) ;
2020-03-20 20:28:31 +00:00
process . exit ( 0 ) ;
2018-09-17 21:09:02 +00:00
}
2020-03-20 20:28:31 +00:00
return opts ;
2018-09-17 21:09:02 +00:00
}
2023-11-27 00:26:33 +00:00
function populateLinterWithArgs ( linter , opts ) {
const extraRoots = opts [ ` ${ linter . key } -roots ` ] ;
if ( extraRoots ) {
linter . roots . push ( ... extraRoots . split ( ',' ) ) ;
}
const extraIgnoreRoots = opts [ ` ${ linter . key } -ignore-roots ` ] ;
if ( extraIgnoreRoots ) {
const list = extraIgnoreRoots . split ( ',' ) ;
if ( linter . ignoreRoots ) {
linter . ignoreRoots . push ( ... list ) ;
} else {
linter . ignoreRoots = list ;
}
}
}
2018-09-17 21:09:02 +00:00
async function findChangedFiles ( top ) {
2020-03-20 20:28:31 +00:00
const result = await GitProcess . exec ( [ 'diff' , '--name-only' , '--cached' ] , top ) ;
2018-09-17 21:09:02 +00:00
if ( result . exitCode !== 0 ) {
2020-03-20 20:28:31 +00:00
console . log ( 'Failed to find changed files' , GitProcess . parseError ( result . stderr ) ) ;
process . exit ( 1 ) ;
2018-09-17 21:09:02 +00:00
}
2020-03-20 20:28:31 +00:00
const relativePaths = result . stdout . split ( /\r\n|\r|\n/g ) ;
const absolutePaths = relativePaths . map ( x => path . join ( top , x ) ) ;
return new Set ( absolutePaths ) ;
2018-09-17 21:09:02 +00:00
}
async function findFiles ( args , linter ) {
2020-03-20 20:28:31 +00:00
let filenames = [ ] ;
2020-06-09 18:29:29 +00:00
let includelist = null ;
2018-09-17 21:09:02 +00:00
2020-06-09 18:29:29 +00:00
// build the includelist
2018-09-17 21:09:02 +00:00
if ( args . changed ) {
2021-11-22 07:34:31 +00:00
includelist = await findChangedFiles ( ELECTRON _ROOT ) ;
2020-06-09 18:29:29 +00:00
if ( ! includelist . size ) {
2020-03-20 20:28:31 +00:00
return [ ] ;
2018-09-17 21:09:02 +00:00
}
2019-01-21 22:46:32 +00:00
} else if ( args . only ) {
2020-06-09 18:29:29 +00:00
includelist = new Set ( args . _ . map ( p => path . resolve ( p ) ) ) ;
2018-09-17 21:09:02 +00:00
}
// accumulate the raw list of files
for ( const root of linter . roots ) {
2021-11-22 07:34:31 +00:00
const files = await findMatchingFiles ( path . join ( ELECTRON _ROOT , root ) , linter . test ) ;
2020-03-20 20:28:31 +00:00
filenames . push ( ... files ) ;
2018-09-17 21:09:02 +00:00
}
2018-09-20 05:41:01 +00:00
for ( const ignoreRoot of ( linter . ignoreRoots ) || [ ] ) {
2021-11-22 07:34:31 +00:00
const ignorePath = path . join ( ELECTRON _ROOT , ignoreRoot ) ;
2020-03-20 20:28:31 +00:00
if ( ! fs . existsSync ( ignorePath ) ) continue ;
2018-09-20 05:41:01 +00:00
2020-03-20 20:28:31 +00:00
const ignoreFiles = new Set ( await findMatchingFiles ( ignorePath , linter . test ) ) ;
filenames = filenames . filter ( fileName => ! ignoreFiles . has ( fileName ) ) ;
2018-09-20 05:41:01 +00:00
}
2020-06-09 18:29:29 +00:00
// remove ignored files
filenames = filenames . filter ( x => ! IGNORELIST . has ( x ) ) ;
2018-09-17 21:09:02 +00:00
2020-06-09 18:29:29 +00:00
// if a includelist exists, remove anything not in it
if ( includelist ) {
filenames = filenames . filter ( x => includelist . has ( x ) ) ;
2018-09-17 21:09:02 +00:00
}
2018-10-16 05:59:45 +00:00
// it's important that filenames be relative otherwise clang-format will
// produce patches with absolute paths in them, which `git apply` will refuse
// to apply.
2021-11-22 07:34:31 +00:00
return filenames . map ( x => path . relative ( ELECTRON _ROOT , x ) ) ;
2018-09-17 21:09:02 +00:00
}
async function main ( ) {
2020-03-20 20:28:31 +00:00
const opts = parseCommandLine ( ) ;
2018-09-17 21:09:02 +00:00
// no mode specified? run 'em all
2023-11-27 00:26:33 +00:00
if ( ! opts . cpp && ! opts . javascript && ! opts . objc && ! opts . python && ! opts . gn && ! opts . patches && ! opts . markdown ) {
opts . cpp = opts . javascript = opts . objc = opts . python = opts . gn = opts . patches = opts . markdown = true ;
2018-09-17 21:09:02 +00:00
}
2020-03-20 20:28:31 +00:00
const linters = LINTERS . filter ( x => opts [ x . key ] ) ;
2018-09-17 21:09:02 +00:00
for ( const linter of linters ) {
2023-11-27 00:26:33 +00:00
populateLinterWithArgs ( linter , opts ) ;
2020-03-20 20:28:31 +00:00
const filenames = await findFiles ( opts , linter ) ;
2018-09-17 21:09:02 +00:00
if ( filenames . length ) {
2020-03-20 20:28:31 +00:00
if ( opts . verbose ) { console . log ( ` linting ${ filenames . length } ${ linter . key } ${ filenames . length === 1 ? 'file' : 'files' } ` ) ; }
2020-12-01 01:47:29 +00:00
await linter . run ( opts , filenames ) ;
2018-09-17 21:09:02 +00:00
}
}
}
2023-06-12 07:55:36 +00:00
if ( require . main === module ) {
2018-09-20 05:41:01 +00:00
main ( ) . catch ( ( error ) => {
2020-03-20 20:28:31 +00:00
console . error ( error ) ;
process . exit ( 1 ) ;
} ) ;
2018-09-17 21:09:02 +00:00
}