Skip to content

Commit 192ac86

Browse files
authored
Set up formatting check and local formatting script (firebase#5530)
1 parent 04295f2 commit 192ac86

File tree

7 files changed

+261
-270
lines changed

7 files changed

+261
-270
lines changed

.github/workflows/format.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Formatting Check (Run yarn format locally if this fails)
2+
3+
on: pull_request
4+
5+
env:
6+
GITHUB_PULL_REQUEST_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
7+
GITHUB_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
8+
9+
jobs:
10+
format:
11+
name: Run license and prettier formatting tasks
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout Repo
16+
uses: actions/checkout@master
17+
with:
18+
# get all history for the diff
19+
fetch-depth: 0
20+
- name: Set up Node (14)
21+
uses: actions/setup-node@v2
22+
with:
23+
node-version: 14.x
24+
- name: Yarn install
25+
run: yarn
26+
- name: Run formatting script
27+
run: yarn format
28+
- name: Check for changes (fail if so)
29+
run: git diff --exit-code
30+
- name: Formatting needs to be updated. See message below.
31+
if: ${{ failure() }}
32+
run: echo "Something was changed by formatting. Run \`yarn format\` locally to do a prettier/license pass. Use \`yarn format --help\` to see options."

package.json

+3-8
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@
4242
"docgen:compat": "yarn docgen:compat:js; yarn docgen:compat:node",
4343
"docgen:compat:js": "node scripts/docgen-compat/generate-docs.js --api js",
4444
"docgen:compat:node": "node scripts/docgen-compat/generate-docs.js --api node",
45-
"prettier": "prettier --config .prettierrc --write '**/*.{ts,js}'",
4645
"lint": "lerna run --scope @firebase/* lint",
4746
"lint:fix": "lerna run --scope @firebase/* lint:fix",
4847
"size-report": "ts-node-script scripts/size_report/report_binary_size.ts",
4948
"modular-export-size-report": "ts-node-script scripts/size_report/report_modular_export_binary_size.ts",
5049
"api-report": "lerna run --scope @firebase/* api-report",
5150
"postinstall": "yarn --cwd repo-scripts/changelog-generator build",
5251
"sa": "ts-node-script repo-scripts/size-analysis/cli.ts",
53-
"api-documenter-devsite": "ts-node-script repo-scripts/api-documenter/src/start.ts"
52+
"api-documenter-devsite": "ts-node-script repo-scripts/api-documenter/src/start.ts",
53+
"format": "ts-node ./scripts/format/format.ts"
5454
},
5555
"repository": {
5656
"type": "git",
@@ -155,10 +155,5 @@
155155
"watch": "1.0.2",
156156
"webpack": "4.46.0",
157157
"yargs": "17.2.0"
158-
},
159-
"husky": {
160-
"hooks": {
161-
"pre-commit": "node tools/gitHooks/precommit.js"
162-
}
163158
}
164-
}
159+
}

scripts/format/format.ts

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { doPrettier } from './prettier';
19+
import { doLicense } from './license';
20+
import { resolve } from 'path';
21+
import simpleGit from 'simple-git/promise';
22+
import chalk from 'chalk';
23+
import glob from 'glob';
24+
import { join } from 'path';
25+
import yargs from 'yargs/yargs';
26+
import { hideBin } from 'yargs/helpers';
27+
28+
// Computed Deps
29+
const root = resolve(__dirname, '../..');
30+
const git = simpleGit(root);
31+
32+
const { path: targetPath, all: runOnAll } = yargs(hideBin(process.argv))
33+
.option('all', {
34+
describe: 'Run on all js/ts files in repo',
35+
type: 'boolean'
36+
})
37+
.option('path', {
38+
describe: 'Specific directory to run on',
39+
type: 'string'
40+
})
41+
.help()
42+
.usage(
43+
`Runs prettier formatting and updates license headers. ` +
44+
`If no arguments are provided it will run formatting on any ` +
45+
`files changed since master.`
46+
)
47+
.parseSync();
48+
49+
const format = async () => {
50+
let changedFiles: string[] | undefined;
51+
try {
52+
if (!runOnAll) {
53+
// If a file pattern is provided, get the individual files.
54+
if (targetPath) {
55+
changedFiles = await new Promise(resolve => {
56+
glob(join(targetPath, '/**/*'), (err, res) => resolve(res));
57+
});
58+
} else {
59+
// Otherwise get all files changed since master.
60+
const baseSha = process.env.GITHUB_PULL_REQUEST_BASE_SHA || 'master';
61+
const diff = await git.diff([
62+
'--name-only',
63+
'--diff-filter=d',
64+
baseSha
65+
]);
66+
changedFiles = diff.split('\n');
67+
68+
if (changedFiles.length === 0) {
69+
console.log(chalk`{green No files changed since ${baseSha}.}`);
70+
return;
71+
}
72+
}
73+
74+
// Only run on .js or .ts files.
75+
changedFiles = changedFiles!.filter(line => line.match(/\.(js|ts)$/));
76+
77+
if (changedFiles.length === 0) {
78+
console.log(chalk`{green No .js or .ts files found in list.`);
79+
return;
80+
}
81+
}
82+
83+
await doPrettier(changedFiles);
84+
85+
await doLicense(changedFiles);
86+
87+
process.exit();
88+
} catch (err) {
89+
console.error(chalk`
90+
{red Formatting failed, error body below}
91+
92+
`);
93+
console.error(err);
94+
return process.exit(1);
95+
}
96+
};
97+
98+
format();

tools/gitHooks/license.js scripts/format/license.ts

+33-43
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,13 @@
1515
* limitations under the License.
1616
*/
1717

18-
const { resolve } = require('path');
19-
const simpleGit = require('simple-git/promise');
20-
const fs = require('mz/fs');
21-
const ora = require('ora');
22-
const chalk = require('chalk');
23-
24-
const root = resolve(__dirname, '../..');
25-
const git = simpleGit(root);
18+
import fs from 'mz/fs';
19+
import chalk from 'chalk';
20+
import glob from 'glob';
21+
2622
const licenseHeader = `/**
2723
* @license
28-
* Copyright 2021 Google LLC
24+
* Copyright ${new Date().getFullYear()} Google LLC
2925
*
3026
* Licensed under the Apache License, Version 2.0 (the "License");
3127
* you may not use this file except in compliance with the License.
@@ -45,15 +41,15 @@ const licenseHeader = `/**
4541
const copyrightPattern = /Copyright \d{4} Google (Inc\.|LLC)/;
4642
const oldCopyrightPattern = /(\s*\*\s*Copyright \d{4}) Google Inc\./;
4743

48-
async function readFiles(paths) {
44+
async function readFiles(paths: string[]) {
4945
const fileContents = await Promise.all(paths.map(path => fs.readFile(path)));
5046
return fileContents.map((buffer, idx) => ({
5147
contents: String(buffer),
5248
path: paths[idx]
5349
}));
5450
}
5551

56-
function addLicenceTag(contents) {
52+
function addLicenseTag(contents: string) {
5753
const lines = contents.split('\n');
5854
let newLines = [];
5955
for (const line of lines) {
@@ -66,7 +62,7 @@ function addLicenceTag(contents) {
6662
return newLines.join('\n');
6763
}
6864

69-
function rewriteCopyrightLine(contents) {
65+
function rewriteCopyrightLine(contents: string) {
7066
const lines = contents.split('\n');
7167
let newLines = lines.map(line => {
7268
return line.replace(oldCopyrightPattern, (_, leader) => {
@@ -76,13 +72,25 @@ function rewriteCopyrightLine(contents) {
7672
return newLines.join('\n');
7773
}
7874

79-
async function doLicense(changedFiles) {
80-
const licenseSpinner = ora(' Validating License Headers').start();
75+
export async function doLicense(changedFiles?: string[]) {
76+
let count = 0;
8177

82-
const paths = changedFiles.filter(line => line.match(/(js|ts)$/));
83-
if (paths.length === 0) return;
78+
let filesToChange: string[] = changedFiles ?? [];
8479

85-
const files = await readFiles(paths);
80+
if (!changedFiles) {
81+
filesToChange = await new Promise(resolve => {
82+
glob(
83+
'+(packages|repo-scripts)/**/*.+(ts|js)',
84+
{ ignore: ['**/node_modules/**', './node_modules/**', '**/dist/**'] },
85+
(err, res) => resolve(res)
86+
);
87+
});
88+
}
89+
90+
console.log(
91+
chalk`{green Validating license headers in ${filesToChange.length} files.}`
92+
);
93+
const files = await readFiles(filesToChange);
8694

8795
await Promise.all(
8896
files.map(({ contents, path }) => {
@@ -91,50 +99,32 @@ async function doLicense(changedFiles) {
9199
// Files with no license block at all.
92100
if (result.match(copyrightPattern) == null) {
93101
result = licenseHeader + result;
102+
console.log(`Adding license to ${path}.`);
94103
}
95104

96105
// Files with no @license tag.
97106
if (result.match(/@license/) == null) {
98-
result = addLicenceTag(result);
107+
result = addLicenseTag(result);
108+
console.log(`Adding @license tag to ${path}.`);
99109
}
100110

101111
// Files with the old form of copyright notice.
102112
if (result.match(oldCopyrightPattern) != null) {
103113
result = rewriteCopyrightLine(result);
114+
console.log(`Updating old copyright notice found in ${path}.`);
104115
}
105116

106117
if (contents !== result) {
118+
count++;
107119
return fs.writeFile(path, result, 'utf8');
108120
} else {
109121
return Promise.resolve();
110122
}
111123
})
112124
);
113-
114-
licenseSpinner.stopAndPersist({
115-
symbol: '✅'
116-
});
117-
118-
// Diff unstaged (license writes) against staged.
119-
const stageDiff = await git.diff(['--name-only']);
120-
121-
if (!stageDiff) {
122-
console.log(chalk`\n{red License pass caused no changes.}\n`);
123-
return;
125+
if (count === 0) {
126+
console.log(chalk`{green No files needed license changes.}`);
124127
} else {
125-
console.log(
126-
`License script modified ${stageDiff.split('\n').length - 1} files.`
127-
);
128+
console.log(chalk`{green ${count} files had license headers updated.}`);
128129
}
129-
130-
const gitSpinner = ora(' Git staging license text modifications.').start();
131-
await git.add('.');
132-
133-
gitSpinner.stopAndPersist({
134-
symbol: '▶️'
135-
});
136130
}
137-
138-
module.exports = {
139-
doLicense
140-
};

scripts/format/prettier.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* @license
3+
* Copyright 2017 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { resolve } from 'path';
19+
import { exec, spawn } from 'child-process-promise';
20+
import chalk from 'chalk';
21+
22+
const root = resolve(__dirname, '../..');
23+
const packageJson = require(root + '/package.json');
24+
25+
async function checkVersion() {
26+
const { stdout } = await exec('yarn prettier --version', {
27+
cwd: root
28+
});
29+
const lines = stdout.split('\n');
30+
let runtimeVersion;
31+
for (const line of lines) {
32+
if (line.match(/^\d+\.\d+\.\d+$/)) {
33+
runtimeVersion = line;
34+
break;
35+
}
36+
}
37+
if (!runtimeVersion) {
38+
console.warn('Was not able to find runtime version of prettier.');
39+
return;
40+
}
41+
const packageVersion = packageJson.devDependencies.prettier;
42+
if (packageVersion !== runtimeVersion) {
43+
const mismatchText =
44+
`Installed version of prettier (${runtimeVersion}) does not match ` +
45+
`required version (${packageVersion}).`;
46+
const versionMismatchMessage = chalk`
47+
{red ${mismatchText}}
48+
49+
{yellow Please re-run {reset 'yarn'} from the root of the repo and try again.}
50+
`;
51+
throw new Error(versionMismatchMessage);
52+
}
53+
}
54+
55+
export async function doPrettier(changedFiles?: string[]) {
56+
try {
57+
await checkVersion();
58+
} catch (e) {
59+
console.error(e);
60+
return process.exit(1);
61+
}
62+
63+
let prettierArgs = [
64+
'prettier',
65+
'--config',
66+
`${resolve(root, '.prettierrc')}`,
67+
'--write'
68+
];
69+
70+
if (changedFiles) {
71+
prettierArgs = [...prettierArgs, ...changedFiles];
72+
console.log(
73+
chalk`{green Validating ${changedFiles.length} files with Prettier}`
74+
);
75+
} else {
76+
prettierArgs.push('{,!(node_modules)/}**/*.{js,ts}');
77+
console.log(chalk`{green Validating all .js and .ts files with Prettier}`);
78+
}
79+
80+
try {
81+
await spawn('yarn', prettierArgs, {
82+
stdio: 'inherit',
83+
cwd: root
84+
});
85+
} catch (e) {
86+
if ((e as NodeJS.ErrnoException).code === 'E2BIG') {
87+
console.error(
88+
chalk`{red Too many files, use a smaller pattern or use the --all flag.}`
89+
);
90+
process.exit();
91+
} else {
92+
throw e;
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)