1*fe5cfb17STomasz Sapeta/** 2*fe5cfb17STomasz Sapeta * Copyright (c) Meta Platforms, Inc. and affiliates. 3*fe5cfb17STomasz Sapeta * 4*fe5cfb17STomasz Sapeta * This source code is licensed under the MIT license found in the 5*fe5cfb17STomasz Sapeta * LICENSE file in the root directory of this source tree. 6*fe5cfb17STomasz Sapeta * 7*fe5cfb17STomasz Sapeta * @format 8*fe5cfb17STomasz Sapeta */ 9*fe5cfb17STomasz Sapeta 10*fe5cfb17STomasz Sapeta'use strict'; 11*fe5cfb17STomasz Sapeta 12*fe5cfb17STomasz Sapetaconst {exec} = require('shelljs'); 13*fe5cfb17STomasz Sapetaconst https = require('https'); 14*fe5cfb17STomasz Sapetaconst fs = require('fs'); 15*fe5cfb17STomasz Sapetaconst path = require('path'); 16*fe5cfb17STomasz Sapetaconst os = require('os'); 17*fe5cfb17STomasz Sapetaconst yargs = require('yargs'); 18*fe5cfb17STomasz Sapeta 19*fe5cfb17STomasz Sapetaconst googleJavaFormatUrl = 20*fe5cfb17STomasz Sapeta 'https://github.com/google/google-java-format/releases/download/google-java-format-1.7/google-java-format-1.7-all-deps.jar'; 21*fe5cfb17STomasz Sapetaconst googleJavaFormatPath = path.join( 22*fe5cfb17STomasz Sapeta os.tmpdir(), 23*fe5cfb17STomasz Sapeta 'google-java-format-all-deps.jar', 24*fe5cfb17STomasz Sapeta); 25*fe5cfb17STomasz Sapetaconst javaFilesCommand = 'find ./ReactAndroid -name "*.java"'; 26*fe5cfb17STomasz Sapeta 27*fe5cfb17STomasz Sapetafunction _download(url, downloadPath, resolve, reject, redirectCount) { 28*fe5cfb17STomasz Sapeta https.get(url, response => { 29*fe5cfb17STomasz Sapeta switch (response.statusCode) { 30*fe5cfb17STomasz Sapeta case 302: //Permanent Redirect 31*fe5cfb17STomasz Sapeta if (redirectCount === 0) { 32*fe5cfb17STomasz Sapeta throw new Error( 33*fe5cfb17STomasz Sapeta `Unhandled response code (HTTP${response.statusCode}) while retrieving google-java-format binary from ${url}`, 34*fe5cfb17STomasz Sapeta ); 35*fe5cfb17STomasz Sapeta } 36*fe5cfb17STomasz Sapeta 37*fe5cfb17STomasz Sapeta _download( 38*fe5cfb17STomasz Sapeta response.headers.location, 39*fe5cfb17STomasz Sapeta downloadPath, 40*fe5cfb17STomasz Sapeta resolve, 41*fe5cfb17STomasz Sapeta reject, 42*fe5cfb17STomasz Sapeta redirectCount - 1, 43*fe5cfb17STomasz Sapeta ); 44*fe5cfb17STomasz Sapeta break; 45*fe5cfb17STomasz Sapeta case 200: //OK 46*fe5cfb17STomasz Sapeta const file = fs.createWriteStream(downloadPath); 47*fe5cfb17STomasz Sapeta 48*fe5cfb17STomasz Sapeta response.pipe(file); 49*fe5cfb17STomasz Sapeta file.on('finish', () => file.close(() => resolve())); 50*fe5cfb17STomasz Sapeta break; 51*fe5cfb17STomasz Sapeta default: 52*fe5cfb17STomasz Sapeta reject( 53*fe5cfb17STomasz Sapeta `Unhandled response code (HTTP${response.statusCode}) while retrieving google-java-format binary from ${url}`, 54*fe5cfb17STomasz Sapeta ); 55*fe5cfb17STomasz Sapeta } 56*fe5cfb17STomasz Sapeta }); 57*fe5cfb17STomasz Sapeta} 58*fe5cfb17STomasz Sapeta 59*fe5cfb17STomasz Sapetafunction download(url, downloadPath) { 60*fe5cfb17STomasz Sapeta return new Promise((resolve, reject) => { 61*fe5cfb17STomasz Sapeta _download(url, downloadPath, resolve, reject, 1); 62*fe5cfb17STomasz Sapeta }); 63*fe5cfb17STomasz Sapeta} 64*fe5cfb17STomasz Sapeta 65*fe5cfb17STomasz Sapetafunction filesWithLintingIssues() { 66*fe5cfb17STomasz Sapeta const proc = exec( 67*fe5cfb17STomasz Sapeta `java -jar ${googleJavaFormatPath} --dry-run $(${javaFilesCommand})`, 68*fe5cfb17STomasz Sapeta {silent: true}, 69*fe5cfb17STomasz Sapeta ); 70*fe5cfb17STomasz Sapeta 71*fe5cfb17STomasz Sapeta if (proc.code !== 0) { 72*fe5cfb17STomasz Sapeta throw new Error(proc.stderr); 73*fe5cfb17STomasz Sapeta } 74*fe5cfb17STomasz Sapeta 75*fe5cfb17STomasz Sapeta return proc.stdout.split('\n').filter(x => x); 76*fe5cfb17STomasz Sapeta} 77*fe5cfb17STomasz Sapeta 78*fe5cfb17STomasz Sapetafunction unifiedDiff(file) { 79*fe5cfb17STomasz Sapeta const lintedProc = exec( 80*fe5cfb17STomasz Sapeta `java -jar ${googleJavaFormatPath} --set-exit-if-changed ${file}`, 81*fe5cfb17STomasz Sapeta {silent: true}, 82*fe5cfb17STomasz Sapeta ); 83*fe5cfb17STomasz Sapeta 84*fe5cfb17STomasz Sapeta //Exit code 1 indicates lint violations, which is what we're expecting 85*fe5cfb17STomasz Sapeta if (lintedProc.code !== 1) { 86*fe5cfb17STomasz Sapeta throw new Error(lintedProc.stderr); 87*fe5cfb17STomasz Sapeta } 88*fe5cfb17STomasz Sapeta 89*fe5cfb17STomasz Sapeta const diffProc = lintedProc.exec(`diff -U 0 ${file} -`, {silent: true}); 90*fe5cfb17STomasz Sapeta 91*fe5cfb17STomasz Sapeta //Exit code 0 if inputs are the same, 1 if different, 2 if trouble. 92*fe5cfb17STomasz Sapeta if (diffProc.code !== 0 && diffProc.code !== 1) { 93*fe5cfb17STomasz Sapeta throw new Error(diffProc.stderr); 94*fe5cfb17STomasz Sapeta } 95*fe5cfb17STomasz Sapeta 96*fe5cfb17STomasz Sapeta return { 97*fe5cfb17STomasz Sapeta file, 98*fe5cfb17STomasz Sapeta diff: diffProc.stdout, 99*fe5cfb17STomasz Sapeta }; 100*fe5cfb17STomasz Sapeta} 101*fe5cfb17STomasz Sapeta 102*fe5cfb17STomasz Sapetafunction extractRangeInformation(range) { 103*fe5cfb17STomasz Sapeta //eg; 104*fe5cfb17STomasz Sapeta // @@ -54 +54,2 @@ 105*fe5cfb17STomasz Sapeta // @@ -1,3 +1,9 @@ 106*fe5cfb17STomasz Sapeta 107*fe5cfb17STomasz Sapeta const regex = /^@@ [-+](\d+,?\d+) [-+](\d+,?\d+) @@$/; 108*fe5cfb17STomasz Sapeta const match = regex.exec(range); 109*fe5cfb17STomasz Sapeta 110*fe5cfb17STomasz Sapeta if (match) { 111*fe5cfb17STomasz Sapeta const original = match[1].split(','); 112*fe5cfb17STomasz Sapeta const updated = match[2].split(','); 113*fe5cfb17STomasz Sapeta 114*fe5cfb17STomasz Sapeta return { 115*fe5cfb17STomasz Sapeta original: { 116*fe5cfb17STomasz Sapeta line: parseInt(original[0], 10), 117*fe5cfb17STomasz Sapeta lineCount: parseInt(original[1], 10) || 1, 118*fe5cfb17STomasz Sapeta }, 119*fe5cfb17STomasz Sapeta updated: { 120*fe5cfb17STomasz Sapeta line: parseInt(updated[0], 10), 121*fe5cfb17STomasz Sapeta lineCount: parseInt(updated[1], 10) || 1, 122*fe5cfb17STomasz Sapeta }, 123*fe5cfb17STomasz Sapeta }; 124*fe5cfb17STomasz Sapeta } 125*fe5cfb17STomasz Sapeta} 126*fe5cfb17STomasz Sapeta 127*fe5cfb17STomasz Sapetafunction parseChanges(file, diff) { 128*fe5cfb17STomasz Sapeta let group = null; 129*fe5cfb17STomasz Sapeta const groups = []; 130*fe5cfb17STomasz Sapeta 131*fe5cfb17STomasz Sapeta diff.split('\n').forEach(line => { 132*fe5cfb17STomasz Sapeta const range = extractRangeInformation(line); 133*fe5cfb17STomasz Sapeta 134*fe5cfb17STomasz Sapeta if (range) { 135*fe5cfb17STomasz Sapeta group = { 136*fe5cfb17STomasz Sapeta range, 137*fe5cfb17STomasz Sapeta description: [line], 138*fe5cfb17STomasz Sapeta }; 139*fe5cfb17STomasz Sapeta groups.push(group); 140*fe5cfb17STomasz Sapeta } else if (group) { 141*fe5cfb17STomasz Sapeta group.description.push(line); 142*fe5cfb17STomasz Sapeta } 143*fe5cfb17STomasz Sapeta }); 144*fe5cfb17STomasz Sapeta 145*fe5cfb17STomasz Sapeta return groups.map(x => ({ 146*fe5cfb17STomasz Sapeta file, 147*fe5cfb17STomasz Sapeta line: x.range.original.line, 148*fe5cfb17STomasz Sapeta lineCount: x.range.original.lineCount, 149*fe5cfb17STomasz Sapeta description: x.description.join('\n'), 150*fe5cfb17STomasz Sapeta })); 151*fe5cfb17STomasz Sapeta} 152*fe5cfb17STomasz Sapeta 153*fe5cfb17STomasz Sapetaasync function main() { 154*fe5cfb17STomasz Sapeta const {argv} = yargs 155*fe5cfb17STomasz Sapeta .scriptName('lint-java') 156*fe5cfb17STomasz Sapeta .usage('Usage: $0 [options]') 157*fe5cfb17STomasz Sapeta .command( 158*fe5cfb17STomasz Sapeta '$0', 159*fe5cfb17STomasz Sapeta 'Downloads the google-java-format package and reformats Java source code to comply with Google Java Style.\n\nSee https://github.com/google/google-java-format', 160*fe5cfb17STomasz Sapeta ) 161*fe5cfb17STomasz Sapeta .option('check', { 162*fe5cfb17STomasz Sapeta type: 'boolean', 163*fe5cfb17STomasz Sapeta description: 164*fe5cfb17STomasz Sapeta 'Outputs a list of files with lint violations.\nExit code is set to 1 if there are violations, otherwise 0.\nDoes not reformat lint issues.', 165*fe5cfb17STomasz Sapeta }) 166*fe5cfb17STomasz Sapeta .option('diff', { 167*fe5cfb17STomasz Sapeta type: 'boolean', 168*fe5cfb17STomasz Sapeta description: 169*fe5cfb17STomasz Sapeta 'Outputs a diff of the lint fix changes in json format.\nDoes not reformat lint issues.', 170*fe5cfb17STomasz Sapeta }); 171*fe5cfb17STomasz Sapeta 172*fe5cfb17STomasz Sapeta await download(googleJavaFormatUrl, googleJavaFormatPath); 173*fe5cfb17STomasz Sapeta 174*fe5cfb17STomasz Sapeta if (argv.check) { 175*fe5cfb17STomasz Sapeta const files = filesWithLintingIssues(); 176*fe5cfb17STomasz Sapeta 177*fe5cfb17STomasz Sapeta files.forEach(x => console.log(x)); 178*fe5cfb17STomasz Sapeta 179*fe5cfb17STomasz Sapeta process.exit(files.length === 0 ? 0 : 1); 180*fe5cfb17STomasz Sapeta 181*fe5cfb17STomasz Sapeta return; 182*fe5cfb17STomasz Sapeta } 183*fe5cfb17STomasz Sapeta 184*fe5cfb17STomasz Sapeta if (argv.diff) { 185*fe5cfb17STomasz Sapeta const suggestions = filesWithLintingIssues() 186*fe5cfb17STomasz Sapeta .map(unifiedDiff) 187*fe5cfb17STomasz Sapeta .filter(x => x) 188*fe5cfb17STomasz Sapeta .map(x => parseChanges(x.file, x.diff)) 189*fe5cfb17STomasz Sapeta .reduce((accumulator, current) => accumulator.concat(current), []); 190*fe5cfb17STomasz Sapeta 191*fe5cfb17STomasz Sapeta console.log(JSON.stringify(suggestions)); 192*fe5cfb17STomasz Sapeta 193*fe5cfb17STomasz Sapeta return; 194*fe5cfb17STomasz Sapeta } 195*fe5cfb17STomasz Sapeta 196*fe5cfb17STomasz Sapeta const proc = exec( 197*fe5cfb17STomasz Sapeta `java -jar ${googleJavaFormatPath} --set-exit-if-changed --replace $(${javaFilesCommand})`, 198*fe5cfb17STomasz Sapeta ); 199*fe5cfb17STomasz Sapeta 200*fe5cfb17STomasz Sapeta process.exit(proc.code); 201*fe5cfb17STomasz Sapeta} 202*fe5cfb17STomasz Sapeta 203*fe5cfb17STomasz Sapeta(async () => { 204*fe5cfb17STomasz Sapeta await main(); 205*fe5cfb17STomasz Sapeta})(); 206