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