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