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