mirror of
https://github.com/github/codeql-action.git
synced 2025-12-27 01:30:10 +08:00
248 lines
7.5 KiB
JavaScript
248 lines
7.5 KiB
JavaScript
'use strict';
|
||
const path = require('path');
|
||
const globby = require('globby');
|
||
const ignoreByDefault = require('ignore-by-default');
|
||
const picomatch = require('picomatch');
|
||
const slash = require('slash');
|
||
const providerManager = require('./provider-manager');
|
||
|
||
const defaultIgnorePatterns = [...ignoreByDefault.directories(), '**/node_modules'];
|
||
const defaultPicomatchIgnorePatterns = [
|
||
...defaultIgnorePatterns,
|
||
// Unlike globby(), picomatch needs a complete pattern when ignoring directories.
|
||
...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`)
|
||
];
|
||
|
||
const defaultMatchNoIgnore = picomatch(defaultPicomatchIgnorePatterns);
|
||
|
||
const defaultIgnoredByWatcherPatterns = [
|
||
'**/*.snap.md', // No need to rerun tests when the Markdown files change.
|
||
'ava.config.js', // Config is not reloaded so avoid rerunning tests when it changes.
|
||
'ava.config.cjs' // Config is not reloaded so avoid rerunning tests when it changes.
|
||
];
|
||
|
||
const buildExtensionPattern = extensions => extensions.length === 1 ? extensions[0] : `{${extensions.join(',')}}`;
|
||
|
||
function normalizePattern(pattern) {
|
||
// Always use `/` in patterns, harmonizing matching across platforms
|
||
if (process.platform === 'win32') {
|
||
pattern = slash(pattern);
|
||
}
|
||
|
||
if (pattern.startsWith('./')) {
|
||
return pattern.slice(2);
|
||
}
|
||
|
||
if (pattern.startsWith('!./')) {
|
||
return `!${pattern.slice(3)}`;
|
||
}
|
||
|
||
return pattern;
|
||
}
|
||
|
||
exports.normalizePattern = normalizePattern;
|
||
|
||
function normalizePatterns(patterns) {
|
||
return patterns.map(pattern => normalizePattern(pattern));
|
||
}
|
||
|
||
exports.normalizePatterns = normalizePatterns;
|
||
|
||
function normalizeGlobs({extensions, files: filePatterns, ignoredByWatcher: ignoredByWatcherPatterns, providers}) {
|
||
if (filePatterns !== undefined && (!Array.isArray(filePatterns) || filePatterns.length === 0)) {
|
||
throw new Error('The ’files’ configuration must be an array containing glob patterns.');
|
||
}
|
||
|
||
if (ignoredByWatcherPatterns !== undefined && (!Array.isArray(ignoredByWatcherPatterns) || ignoredByWatcherPatterns.length === 0)) {
|
||
throw new Error('The ’ignoredByWatcher’ configuration must be an array containing glob patterns.');
|
||
}
|
||
|
||
const extensionPattern = buildExtensionPattern(extensions);
|
||
const defaultTestPatterns = [
|
||
`test.${extensionPattern}`,
|
||
`{src,source}/test.${extensionPattern}`,
|
||
`**/__tests__/**/*.${extensionPattern}`,
|
||
`**/*.spec.${extensionPattern}`,
|
||
`**/*.test.${extensionPattern}`,
|
||
`**/test-*.${extensionPattern}`,
|
||
`**/test/**/*.${extensionPattern}`,
|
||
`**/tests/**/*.${extensionPattern}`,
|
||
'!**/__tests__/**/__{helper,fixture}?(s)__/**/*',
|
||
'!**/test?(s)/**/{helper,fixture}?(s)/**/*'
|
||
];
|
||
|
||
if (filePatterns) {
|
||
filePatterns = normalizePatterns(filePatterns);
|
||
|
||
if (filePatterns.every(pattern => pattern.startsWith('!'))) {
|
||
// Use defaults if patterns only contains exclusions.
|
||
filePatterns = [...defaultTestPatterns, ...filePatterns];
|
||
}
|
||
} else {
|
||
filePatterns = defaultTestPatterns;
|
||
}
|
||
|
||
if (ignoredByWatcherPatterns) {
|
||
ignoredByWatcherPatterns = [...defaultIgnoredByWatcherPatterns, ...normalizePatterns(ignoredByWatcherPatterns)];
|
||
} else {
|
||
ignoredByWatcherPatterns = [...defaultIgnoredByWatcherPatterns];
|
||
}
|
||
|
||
for (const {level, main} of providers) {
|
||
if (level >= providerManager.levels.pathRewrites) {
|
||
({filePatterns, ignoredByWatcherPatterns} = main.updateGlobs({filePatterns, ignoredByWatcherPatterns}));
|
||
}
|
||
}
|
||
|
||
return {extensions, filePatterns, ignoredByWatcherPatterns};
|
||
}
|
||
|
||
exports.normalizeGlobs = normalizeGlobs;
|
||
|
||
const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1));
|
||
|
||
exports.hasExtension = hasExtension;
|
||
|
||
const globFiles = async (cwd, patterns) => {
|
||
const files = await globby(patterns, {
|
||
// Globs should work relative to the cwd value only (this should be the
|
||
// project directory that AVA is run in).
|
||
absolute: false,
|
||
braceExpansion: true,
|
||
caseSensitiveMatch: false,
|
||
cwd,
|
||
dot: false,
|
||
expandDirectories: false,
|
||
extglob: true,
|
||
followSymbolicLinks: true,
|
||
gitignore: false,
|
||
globstar: true,
|
||
ignore: defaultIgnorePatterns,
|
||
baseNameMatch: false,
|
||
onlyFiles: true,
|
||
stats: false,
|
||
unique: true
|
||
});
|
||
|
||
// Return absolute file paths. This has the side-effect of normalizing paths
|
||
// on Windows.
|
||
return files.map(file => path.join(cwd, file));
|
||
};
|
||
|
||
async function findFiles({cwd, extensions, filePatterns}) {
|
||
return (await globFiles(cwd, filePatterns)).filter(file => hasExtension(extensions, file));
|
||
}
|
||
|
||
exports.findFiles = findFiles;
|
||
|
||
async function findTests({cwd, extensions, filePatterns}) {
|
||
return (await findFiles({cwd, extensions, filePatterns})).filter(file => !path.basename(file).startsWith('_'));
|
||
}
|
||
|
||
exports.findTests = findTests;
|
||
|
||
function getChokidarIgnorePatterns({ignoredByWatcherPatterns}) {
|
||
return [
|
||
...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`),
|
||
...ignoredByWatcherPatterns.filter(pattern => !pattern.startsWith('!'))
|
||
];
|
||
}
|
||
|
||
exports.getChokidarIgnorePatterns = getChokidarIgnorePatterns;
|
||
|
||
const matchingCache = new WeakMap();
|
||
const processMatchingPatterns = input => {
|
||
let result = matchingCache.get(input);
|
||
if (!result) {
|
||
const ignore = [...defaultPicomatchIgnorePatterns];
|
||
const patterns = input.filter(pattern => {
|
||
if (pattern.startsWith('!')) {
|
||
// Unlike globby(), picomatch needs a complete pattern when ignoring directories.
|
||
ignore.push(pattern.slice(1), `${pattern.slice(1)}/**/*`);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
});
|
||
|
||
result = {
|
||
match: picomatch(patterns, {ignore}),
|
||
matchNoIgnore: picomatch(patterns)
|
||
};
|
||
matchingCache.set(input, result);
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
function matches(file, patterns) {
|
||
const {match} = processMatchingPatterns(patterns);
|
||
return match(file);
|
||
}
|
||
|
||
exports.matches = matches;
|
||
|
||
const matchesIgnorePatterns = (file, patterns) => {
|
||
const {matchNoIgnore} = processMatchingPatterns(patterns);
|
||
return matchNoIgnore(file) || defaultMatchNoIgnore(file);
|
||
};
|
||
|
||
function normalizeFileForMatching(cwd, file) {
|
||
if (process.platform === 'win32') {
|
||
cwd = slash(cwd);
|
||
file = slash(file);
|
||
}
|
||
|
||
if (!cwd) { // TODO: Ensure tests provide an actual value.
|
||
return file;
|
||
}
|
||
|
||
// TODO: If `file` is outside `cwd` we can't normalize it. Need to figure
|
||
// out if that's a real-world scenario, but we may have to ensure the file
|
||
// isn't even selected.
|
||
if (!file.startsWith(cwd)) {
|
||
return file;
|
||
}
|
||
|
||
// Assume `cwd` does *not* end in a slash.
|
||
return file.slice(cwd.length + 1);
|
||
}
|
||
|
||
exports.normalizeFileForMatching = normalizeFileForMatching;
|
||
|
||
function isHelperish(file) { // Assume file has been normalized already.
|
||
// File names starting with an underscore are deemed "helpers".
|
||
if (path.basename(file).startsWith('_')) {
|
||
return true;
|
||
}
|
||
|
||
// This function assumes the file has been normalized. If it couldn't be,
|
||
// don't check if it's got a parent directory that starts with an underscore.
|
||
// Deem it not a "helper".
|
||
if (path.isAbsolute(file)) {
|
||
return false;
|
||
}
|
||
|
||
// If the file has a parent directory that starts with only a single
|
||
// underscore, it's deemed a "helper".
|
||
return path.dirname(file).split('/').some(dir => /^_(?:$|[^_])/.test(dir));
|
||
}
|
||
|
||
exports.isHelperish = isHelperish;
|
||
|
||
function classify(file, {cwd, extensions, filePatterns, ignoredByWatcherPatterns}) {
|
||
file = normalizeFileForMatching(cwd, file);
|
||
return {
|
||
isIgnoredByWatcher: matchesIgnorePatterns(file, ignoredByWatcherPatterns),
|
||
isTest: hasExtension(extensions, file) && !isHelperish(file) && filePatterns.length > 0 && matches(file, filePatterns)
|
||
};
|
||
}
|
||
|
||
exports.classify = classify;
|
||
|
||
function applyTestFileFilter({cwd, filter, testFiles}) {
|
||
return testFiles.filter(file => matches(normalizeFileForMatching(cwd, file), filter));
|
||
}
|
||
|
||
exports.applyTestFileFilter = applyTestFileFilter;
|