Files
react-native-devtools-frontend/scripts/test/run_header_check_non_js_files.js
T
Tim van der Lippe a53672de83 Check license headers for CSS files during PRESUBMIT
This mirrors the license header checks we perform for
Python-like files and apply the same logic for CSS.

To do so, we modify the relevant regexes and patch
function to use the correct version of comment structure
for CSS files (which use `/*` rather than `#` as delimiters).

R=jacktfranklin@chromium.org

Bug: 1106746
Change-Id: I51d51e9b793a1015aeb20a3b3a55e075c2ee748f
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3014756
Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org>
Auto-Submit: Tim van der Lippe <tvanderlippe@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
2021-07-13 12:28:56 +00:00

148 lines
5.2 KiB
JavaScript

// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Script that can automatically add missing license headers to non-JS
// files, including `.gn`, `.py`, `.gni`, `.css` files.
// @ts-check
const readline = require('readline');
const path = require('path');
const fs = require('fs');
const glob = require('glob');
const ROOT_DIRECTORY = path.join(__dirname, '..', '..');
const FRONT_END_DIRECTORY = path.join(ROOT_DIRECTORY, 'front_end');
const TEST_DIRECTORY = path.join(ROOT_DIRECTORY, 'test');
const SCRIPTS_DIRECTORY = path.join(ROOT_DIRECTORY, 'scripts');
const INSPECTOR_OVERLAY_DIRECTORY = path.join(ROOT_DIRECTORY, 'inspector_overlay');
const CONFIG_DIRECTORY = path.join(ROOT_DIRECTORY, 'config');
const CURRENT_YEAR = new Date().getFullYear();
const LINE_LICENSE_HEADER = [
`Copyright ${CURRENT_YEAR} The Chromium Authors. All rights reserved.`,
'Use of this source code is governed by a BSD-style license that can be',
'found in the LICENSE file.',
];
const LINE_REGEXES = LINE_LICENSE_HEADER.map(line => line.replace(String(CURRENT_YEAR), '(\\(c\\) )?\\d{4}') + '$');
const PYTHON_LIKE_LINE_REGEXES = LINE_REGEXES.map(line => new RegExp('# ' + line));
/** @type {!Array<!RegExp>} */
const CSS_LIKE_LINE_REGEXES = [];
CSS_LIKE_LINE_REGEXES.push(new RegExp('\\/\\*'));
for (const line of LINE_REGEXES) {
CSS_LIKE_LINE_REGEXES.push(new RegExp(` \\* ${line}`));
}
CSS_LIKE_LINE_REGEXES.push(new RegExp(' \\*\\/'));
const EXEMPTED_FILES = new Set([
['front_end', 'panels', 'application', 'indexedDBViews.css'],
['front_end', 'panels', 'application', 'resourcesPanel.css'],
['front_end', 'panels', 'console', 'consoleView.css'],
['front_end', 'panels', 'elements', 'elementsPanel.css'],
['front_end', 'panels', 'network', 'networkLogView.css'],
['front_end', 'panels', 'network', 'networkPanel.css'],
['front_end', 'panels', 'profiler', 'heapProfiler.css'],
['front_end', 'panels', 'profiler', 'profilesPanel.css'],
['front_end', 'panels', 'screencast', 'screencastView.css'],
['front_end', 'panels', 'sources', 'navigatorTree.css'],
['front_end', 'panels', 'sources', 'sourcesPanel.css'],
['front_end', 'panels', 'sources', 'sourcesView.css'],
['front_end', 'panels', 'timeline', 'timelinePanel.css'],
['front_end', 'ui', 'legacy', 'filter.css'],
['front_end', 'ui', 'legacy', 'inspectorSyntaxHighlight.css'],
['front_end', 'ui', 'legacy', 'splitWidget.css'],
['front_end', 'ui', 'legacy', 'suggestBox.css'],
['front_end', 'ui', 'legacy', 'tabbedPane.css'],
['front_end', 'ui', 'legacy', 'viewContainers.css'],
].map(fileLocation => fileLocation.join('/')));
/**
* @param {string} fileLocation
* @returns {boolean}
*/
function isPythonLikeFile(fileLocation) {
return ['.gn', '.gni'].includes(path.extname(fileLocation));
}
/**
* @param {string} fileLocation
*/
async function checkAndMaybeAddLicenseHeader(fileLocation) {
const fileStream = fs.createReadStream(fileLocation);
const fileReader = readline.createInterface({input: fileStream});
const lineRegexes = isPythonLikeFile(fileLocation) ? PYTHON_LIKE_LINE_REGEXES : CSS_LIKE_LINE_REGEXES;
try {
await new Promise((resolve, reject) => {
let i = 0;
fileReader.on('line', line => {
if (lineRegexes[i].exec(line)) {
i++;
if (i === lineRegexes.length) {
fileReader.close();
fileReader.removeAllListeners();
resolve();
}
} else {
reject(`Invalid license header for file ${fileLocation}`);
}
});
});
} catch (checkError) {
try {
fixLicenseHeaderForFile(fileLocation);
} catch (writeError) {
console.error('Unable to automatically fix license header for file');
console.error(`Error when checking: ${checkError}`);
console.error(`Error when writing: ${writeError}`);
process.exit(1);
}
} finally {
fileStream.close();
}
}
/**
* @param {string} fileLocation
*/
function fixLicenseHeaderForFile(fileLocation) {
/** @type {!Array<string>} */
let newLicenseHeaderLines;
if (isPythonLikeFile(fileLocation)) {
newLicenseHeaderLines = LINE_LICENSE_HEADER.map(line => `# ${line}`);
} else {
newLicenseHeaderLines = ['/*'];
for (const line of LINE_LICENSE_HEADER) {
newLicenseHeaderLines.push(` * ${line}`);
}
newLicenseHeaderLines.push(' */');
}
fs.writeFileSync(
fileLocation,
Buffer.concat([Buffer.from(newLicenseHeaderLines.join('\n') + '\n\n'), fs.readFileSync(fileLocation)]));
}
let filesToLint = process.argv.slice(2);
if (filesToLint.length === 0) {
const topLevelDirectories =
[FRONT_END_DIRECTORY, SCRIPTS_DIRECTORY, TEST_DIRECTORY, INSPECTOR_OVERLAY_DIRECTORY, CONFIG_DIRECTORY].join(',');
filesToLint = glob.sync(`{${topLevelDirectories}}/**/{BUILD.gn,*.gni,*.css}`);
}
for (const fileLocation of filesToLint) {
const relativeFileLocation = path.relative(ROOT_DIRECTORY, fileLocation).replace(/\\/g, '/');
if (relativeFileLocation.startsWith('front_end/third_party')) {
continue;
}
if (EXEMPTED_FILES.has(relativeFileLocation)) {
continue;
}
checkAndMaybeAddLicenseHeader(fileLocation);
}