1 Star 0 Fork 0

dssljt/eslint

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
Makefile.js 36.24 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146
/**
* @fileoverview Build file
* @author nzakas
*/
/* global target */
/* eslint no-use-before-define: "off", no-console: "off" */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
require("shelljs/make");
const lodash = require("lodash"),
checker = require("npm-license"),
ReleaseOps = require("eslint-release"),
dateformat = require("dateformat"),
fs = require("fs"),
glob = require("glob"),
markdownlint = require("markdownlint"),
os = require("os"),
path = require("path"),
semver = require("semver"),
ejs = require("ejs"),
loadPerf = require("load-perf"),
yaml = require("js-yaml");
const { cat, cd, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, test } = require("shelljs");
//------------------------------------------------------------------------------
// Settings
//------------------------------------------------------------------------------
/*
* A little bit fuzzy. My computer has a first CPU speed of 3392 and the perf test
* always completes in < 3800ms. However, Travis is less predictable due to
* multiple different VM types. So I'm fudging this for now in the hopes that it
* at least provides some sort of useful signal.
*/
const PERF_MULTIPLIER = 13e6;
const OPEN_SOURCE_LICENSES = [
/MIT/, /BSD/, /Apache/, /ISC/, /WTF/, /Public Domain/, /LGPL/
];
//------------------------------------------------------------------------------
// Data
//------------------------------------------------------------------------------
const NODE = "node ", // intentional extra space
NODE_MODULES = "./node_modules/",
TEMP_DIR = "./tmp/",
DEBUG_DIR = "./debug/",
BUILD_DIR = "./build/",
DOCS_DIR = "../eslint.github.io/docs",
SITE_DIR = "../eslint.github.io/",
PERF_TMP_DIR = path.join(TEMP_DIR, "eslint", "performance"),
// Utilities - intentional extra space at the end of each string
MOCHA = `${NODE_MODULES}mocha/bin/_mocha `,
ESLINT = `${NODE} bin/eslint.js --report-unused-disable-directives `,
// Files
MAKEFILE = "./Makefile.js",
JS_FILES = "\"lib/**/*.js\" \"conf/**/*.js\" \"bin/**/*.js\" \"tools/**/*.js\"",
JSON_FILES = find("conf/").filter(fileType("json")),
MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")),
TEST_FILES = getTestFilePatterns(),
PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslintrc.yml"),
PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"),
PERF_MULTIFILES_TARGETS = `"${PERF_MULTIFILES_TARGET_DIR + path.sep}{lib,tests${path.sep}lib}${path.sep}**${path.sep}*.js"`,
// Settings
MOCHA_TIMEOUT = 10000;
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Generates file patterns for test files
* @returns {string} test file patterns
* @private
*/
function getTestFilePatterns() {
return ls("tests/lib/").filter(pathToCheck => test("-d", `tests/lib/${pathToCheck}`)).reduce((initialValue, currentValues) => {
if (currentValues !== "rules") {
initialValue.push(`"tests/lib/${currentValues}/**/*.js"`);
}
return initialValue;
}, ["\"tests/lib/rules/**/*.js\"", "\"tests/lib/*.js\"", "\"tests/bin/**/*.js\"", "\"tests/tools/**/*.js\""]).join(" ");
}
/**
* Simple JSON file validation that relies on ES JSON parser.
* @param {string} filePath Path to JSON.
* @throws Error If file contents is invalid JSON.
* @returns {undefined}
*/
function validateJsonFile(filePath) {
const contents = fs.readFileSync(filePath, "utf8");
JSON.parse(contents);
}
/**
* Generates a function that matches files with a particular extension.
* @param {string} extension The file extension (i.e. "js")
* @returns {Function} The function to pass into a filter method.
* @private
*/
function fileType(extension) {
return function(filename) {
return filename.slice(filename.lastIndexOf(".") + 1) === extension;
};
}
/**
* Generates a static file that includes each rule by name rather than dynamically
* looking up based on directory. This is used for the browser version of ESLint.
* @param {string} basedir The directory in which to look for code.
* @returns {void}
*/
function generateRulesIndex(basedir) {
let output = "module.exports = function() {\n";
output += " var rules = Object.create(null);\n";
find(`${basedir}rules/`).filter(fileType("js")).forEach(filename => {
const basename = path.basename(filename, ".js");
output += ` rules["${basename}"] = require("./rules/${basename}");\n`;
});
output += "\n return rules;\n};";
output.to(`${basedir}load-rules.js`);
}
/**
* Executes a command and returns the output instead of printing it to stdout.
* @param {string} cmd The command string to execute.
* @returns {string} The result of the executed command.
*/
function execSilent(cmd) {
return exec(cmd, { silent: true }).stdout;
}
/**
* Generates a release blog post for eslint.org
* @param {Object} releaseInfo The release metadata.
* @returns {void}
* @private
*/
function generateBlogPost(releaseInfo) {
const ruleList = ls("lib/rules")
// Strip the .js extension
.map(ruleFileName => ruleFileName.replace(/\.js$/, ""))
/*
* Sort by length descending. This ensures that rule names which are substrings of other rule names are not
* matched incorrectly. For example, the string "no-undefined" should get matched with the `no-undefined` rule,
* instead of getting matched with the `no-undef` rule followed by the string "ined".
*/
.sort((ruleA, ruleB) => ruleB.length - ruleA.length);
const renderContext = Object.assign({ prereleaseMajorVersion: null, ruleList }, releaseInfo);
const output = ejs.render(cat("./templates/blogpost.md.ejs"), renderContext),
now = new Date(),
month = now.getMonth() + 1,
day = now.getDate(),
filename = `../eslint.github.io/_posts/${now.getFullYear()}-${
month < 10 ? `0${month}` : month}-${
day < 10 ? `0${day}` : day}-eslint-v${
releaseInfo.version}-released.md`;
output.to(filename);
}
/**
* Generates a doc page with formatter result examples
* @param {Object} formatterInfo Linting results from each formatter
* @param {string} [prereleaseVersion] The version used for a prerelease. This
* changes where the output is stored.
* @returns {void}
*/
function generateFormatterExamples(formatterInfo, prereleaseVersion) {
const output = ejs.render(cat("./templates/formatter-examples.md.ejs"), formatterInfo);
let filename = "../eslint.github.io/docs/user-guide/formatters/index.md",
htmlFilename = "../eslint.github.io/docs/user-guide/formatters/html-formatter-example.html";
if (prereleaseVersion) {
filename = filename.replace("/docs", `/docs/${prereleaseVersion}`);
htmlFilename = htmlFilename.replace("/docs", `/docs/${prereleaseVersion}`);
if (!test("-d", path.dirname(filename))) {
mkdir(path.dirname(filename));
}
}
output.to(filename);
formatterInfo.formatterResults.html.result.to(htmlFilename);
}
/**
* Generate a doc page that lists all of the rules and links to them
* @param {string} basedir The directory in which to look for code.
* @returns {void}
*/
function generateRuleIndexPage(basedir) {
const outputFile = "../eslint.github.io/_data/rules.yml",
categoryList = "conf/category-list.json",
categoriesData = JSON.parse(cat(path.resolve(categoryList)));
find(path.join(basedir, "/lib/rules/")).filter(fileType("js"))
.map(filename => [filename, path.basename(filename, ".js")])
.sort((a, b) => a[1].localeCompare(b[1]))
.forEach(pair => {
const filename = pair[0];
const basename = pair[1];
const rule = require(filename);
if (rule.meta.deprecated) {
categoriesData.deprecated.rules.push({
name: basename,
replacedBy: rule.meta.docs.replacedBy || []
});
} else {
const output = {
name: basename,
description: rule.meta.docs.description,
recommended: rule.meta.docs.recommended || false,
fixable: !!rule.meta.fixable
},
category = lodash.find(categoriesData.categories, { name: rule.meta.docs.category });
if (!category.rules) {
category.rules = [];
}
category.rules.push(output);
}
});
const output = yaml.safeDump(categoriesData, { sortKeys: true });
output.to(outputFile);
}
/**
* Commits the changes in the site and publishes them to GitHub.
* @param {string} [tag] The string to tag the commit with.
* @returns {void}
*/
function publishSite(tag) {
const currentDir = pwd();
cd(SITE_DIR);
exec("git add -A .");
exec(`git commit -m "Autogenerated new docs and demo at ${dateformat(new Date())}"`);
if (tag) {
exec(`git tag ${tag}`);
}
exec("git fetch origin && git rebase origin/master");
exec("git push origin master --tags");
cd(currentDir);
}
/**
* Creates a release version tag and pushes to origin.
* @param {boolean} [ciRelease] Set to true to indicate this is a CI release.
* @returns {void}
*/
function release(ciRelease) {
const releaseInfo = ReleaseOps.release(null, ciRelease);
echo("Generating site");
target.gensite();
generateBlogPost(releaseInfo);
publishSite(`v${releaseInfo.version}`);
echo("Site has been published");
echo("Publishing to GitHub");
ReleaseOps.publishReleaseToGitHub(releaseInfo);
}
/**
* Creates a prerelease version tag and pushes to origin.
* @param {string} prereleaseId The prerelease identifier (alpha, beta, etc.)
* @returns {void}
*/
function prerelease(prereleaseId) {
const releaseInfo = ReleaseOps.release(prereleaseId);
const nextMajor = semver.inc(releaseInfo.version, "major");
/*
* Premajor release should have identical "next major version".
* Preminor and prepatch release will not.
* 5.0.0-alpha.0 --> next major = 5, current major = 5
* 4.4.0-alpha.0 --> next major = 5, current major = 4
* 4.0.1-alpha.0 --> next major = 5, current major = 4
*/
if (semver.major(releaseInfo.version) === semver.major(nextMajor)) {
/*
* This prerelease is for a major release (not preminor/prepatch).
* Blog post generation logic needs to be aware of this (as well as
* know what the next major version is actually supposed to be).
*/
releaseInfo.prereleaseMajorVersion = nextMajor;
}
echo("Generating site");
// always write docs into the next major directory (so 2.0.0-alpha.0 writes to 2.0.0)
target.gensite(nextMajor);
generateBlogPost(releaseInfo);
publishSite(`v${releaseInfo.version}`);
echo("Site has been published");
echo("Publishing to GitHub");
ReleaseOps.publishReleaseToGitHub(releaseInfo);
}
/**
* Splits a command result to separate lines.
* @param {string} result The command result string.
* @returns {array} The separated lines.
*/
function splitCommandResultToLines(result) {
return result.trim().split("\n");
}
/**
* Gets the first commit sha of the given file.
* @param {string} filePath The file path which should be checked.
* @returns {string} The commit sha.
*/
function getFirstCommitOfFile(filePath) {
let commits = execSilent(`git rev-list HEAD -- ${filePath}`);
commits = splitCommandResultToLines(commits);
return commits[commits.length - 1].trim();
}
/**
* Gets the tag name where a given file was introduced first.
* @param {string} filePath The file path to check.
* @returns {string} The tag name.
*/
function getFirstVersionOfFile(filePath) {
const firstCommit = getFirstCommitOfFile(filePath);
let tags = execSilent(`git tag --contains ${firstCommit}`);
tags = splitCommandResultToLines(tags);
return tags.reduce((list, version) => {
const validatedVersion = semver.valid(version.trim());
if (validatedVersion) {
list.push(validatedVersion);
}
return list;
}, []).sort(semver.compare)[0];
}
/**
* Gets the commit that deleted a file.
* @param {string} filePath The path to the deleted file.
* @returns {string} The commit sha.
*/
function getCommitDeletingFile(filePath) {
const commits = execSilent(`git rev-list HEAD -- ${filePath}`);
return splitCommandResultToLines(commits)[0];
}
/**
* Gets the first version number where a given file is no longer present.
* @param {string} filePath The path to the deleted file.
* @returns {string} The version number.
*/
function getFirstVersionOfDeletion(filePath) {
const deletionCommit = getCommitDeletingFile(filePath),
tags = execSilent(`git tag --contains ${deletionCommit}`);
return splitCommandResultToLines(tags)
.map(version => semver.valid(version.trim()))
.filter(version => version)
.sort(semver.compare)[0];
}
/**
* Lints Markdown files.
* @param {array} files Array of file names to lint.
* @returns {Object} exec-style exit code object.
* @private
*/
function lintMarkdown(files) {
const config = {
default: true,
// Exclusions for deliberate/widespread violations
MD001: false, // Header levels should only increment by one level at a time
MD002: false, // First header should be a h1 header
MD007: { // Unordered list indentation
indent: 4
},
MD012: false, // Multiple consecutive blank lines
MD013: false, // Line length
MD014: false, // Dollar signs used before commands without showing output
MD019: false, // Multiple spaces after hash on atx style header
MD021: false, // Multiple spaces inside hashes on closed atx style header
MD024: false, // Multiple headers with the same content
MD026: false, // Trailing punctuation in header
MD029: false, // Ordered list item prefix
MD030: false, // Spaces after list markers
MD033: false, // Allow inline HTML
MD034: false, // Bare URL used
MD040: false, // Fenced code blocks should have a language specified
MD041: false // First line in file should be a top level header
},
result = markdownlint.sync({
files,
config,
resultVersion: 1
}),
resultString = result.toString(),
returnCode = resultString ? 1 : 0;
if (resultString) {
console.error(resultString);
}
return { code: returnCode };
}
/**
* Gets linting results from every formatter, based on a hard-coded snippet and config
* @returns {Object} Output from each formatter
*/
function getFormatterResults() {
const CLIEngine = require("./lib/cli-engine"),
stripAnsi = require("strip-ansi");
const formatterFiles = fs.readdirSync("./lib/formatters/"),
cli = new CLIEngine({
useEslintrc: false,
baseConfig: { extends: "eslint:recommended" },
rules: {
"no-else-return": 1,
indent: [1, 4],
"space-unary-ops": 2,
semi: [1, "always"],
"consistent-return": 2
}
}),
codeString = [
"function addOne(i) {",
" if (i != NaN) {",
" return i ++",
" } else {",
" return",
" }",
"};"
].join("\n"),
rawMessages = cli.executeOnText(codeString, "fullOfProblems.js", true);
return formatterFiles.reduce((data, filename) => {
const fileExt = path.extname(filename),
name = path.basename(filename, fileExt);
if (fileExt === ".js") {
data.formatterResults[name] = {
result: stripAnsi(cli.getFormatter(name)(rawMessages.results))
};
}
return data;
}, { formatterResults: {} });
}
/**
* Gets a path to an executable in node_modules/.bin
* @param {string} command The executable name
* @returns {string} The executable path
*/
function getBinFile(command) {
return path.join("node_modules", ".bin", command);
}
//------------------------------------------------------------------------------
// Tasks
//------------------------------------------------------------------------------
target.all = function() {
target.test();
};
target.lint = function() {
let errors = 0,
lastReturn;
echo("Validating Makefile.js");
lastReturn = exec(`${ESLINT} ${MAKEFILE}`);
if (lastReturn.code !== 0) {
errors++;
}
echo("Validating .eslintrc.js");
lastReturn = exec(`${ESLINT} .eslintrc.js`);
if (lastReturn.code !== 0) {
errors++;
}
echo("Validating JSON Files");
lodash.forEach(JSON_FILES, validateJsonFile);
echo("Validating Markdown Files");
lastReturn = lintMarkdown(MARKDOWN_FILES_ARRAY);
if (lastReturn.code !== 0) {
errors++;
}
echo("Validating JavaScript files");
lastReturn = exec(`${ESLINT} ${JS_FILES}`);
if (lastReturn.code !== 0) {
errors++;
}
echo("Validating JavaScript test files");
lastReturn = exec(`${ESLINT} "tests/**/*.js"`);
if (lastReturn.code !== 0) {
errors++;
}
if (errors) {
exit(1);
}
};
target.fuzz = function() {
const fuzzerRunner = require("./tools/fuzzer-runner");
const fuzzResults = fuzzerRunner.run({ amount: process.env.CI ? 1000 : 300 });
if (fuzzResults.length) {
echo(`The fuzzer reported ${fuzzResults.length} error${fuzzResults.length === 1 ? "" : "s"}.`);
const formattedResults = JSON.stringify({ results: fuzzResults }, null, 4);
if (process.env.CI) {
echo("More details can be found below.");
echo(formattedResults);
} else {
if (!test("-d", DEBUG_DIR)) {
mkdir(DEBUG_DIR);
}
let fuzzLogPath;
let fileSuffix = 0;
// To avoid overwriting any existing fuzzer log files, append a numeric suffix to the end of the filename.
do {
fuzzLogPath = path.join(DEBUG_DIR, `fuzzer-log-${fileSuffix}.json`);
fileSuffix++;
} while (test("-f", fuzzLogPath));
formattedResults.to(fuzzLogPath);
// TODO: (not-an-aardvark) Create a better way to isolate and test individual fuzzer errors from the log file
echo(`More details can be found in ${fuzzLogPath}.`);
}
exit(1);
}
};
target.test = function() {
target.lint();
target.checkRuleFiles();
let errors = 0,
lastReturn;
echo("Running unit tests");
lastReturn = exec(`${getBinFile("istanbul")} cover ${MOCHA} -- -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`);
if (lastReturn.code !== 0) {
errors++;
}
lastReturn = exec(`${getBinFile("istanbul")} check-coverage --statement 99 --branch 98 --function 99 --lines 99`);
if (lastReturn.code !== 0) {
errors++;
}
target.browserify();
lastReturn = exec(`${getBinFile("karma")} start karma.conf.js`);
if (lastReturn.code !== 0) {
errors++;
}
if (errors) {
exit(1);
}
target.checkLicenses();
};
target.docs = function() {
echo("Generating documentation");
exec(`${getBinFile("jsdoc")} -d jsdoc lib`);
echo("Documentation has been output to /jsdoc");
};
target.gensite = function(prereleaseVersion) {
echo("Generating eslint.org");
let docFiles = [
"/rules/",
"/user-guide/",
"/maintainer-guide/",
"/developer-guide/",
"/about/"
];
// append version
if (prereleaseVersion) {
docFiles = docFiles.map(docFile => `/${prereleaseVersion}${docFile}`);
}
// 1. create temp and build directory
echo("> Creating a temporary directory (Step 1)");
if (!test("-d", TEMP_DIR)) {
mkdir(TEMP_DIR);
}
// 2. remove old files from the site
echo("> Removing old files (Step 2)");
docFiles.forEach(filePath => {
const fullPath = path.join(DOCS_DIR, filePath),
htmlFullPath = fullPath.replace(".md", ".html");
if (test("-f", fullPath)) {
rm("-rf", fullPath);
if (filePath.indexOf(".md") >= 0 && test("-f", htmlFullPath)) {
rm("-rf", htmlFullPath);
}
}
});
// 3. Copy docs folder to a temporary directory
echo("> Copying the docs folder (Step 3)");
cp("-rf", "docs/*", TEMP_DIR);
let versions = test("-f", "./versions.json") ? JSON.parse(cat("./versions.json")) : {};
if (!versions.added) {
versions = {
added: versions,
removed: {}
};
}
const rules = require(".").linter.getRules();
const RECOMMENDED_TEXT = "\n\n(recommended) The `\"extends\": \"eslint:recommended\"` property in a configuration file enables this rule.";
const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.";
// 4. Loop through all files in temporary directory
process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r");
const tempFiles = find(TEMP_DIR);
const length = tempFiles.length;
tempFiles.forEach((filename, i) => {
if (test("-f", filename) && path.extname(filename) === ".md") {
const rulesUrl = "https://github.com/eslint/eslint/tree/master/lib/rules/",
docsUrl = "https://github.com/eslint/eslint/tree/master/docs/rules/",
baseName = path.basename(filename),
sourceBaseName = `${path.basename(filename, ".md")}.js`,
sourcePath = path.join("lib/rules", sourceBaseName),
ruleName = path.basename(filename, ".md"),
filePath = path.join("docs", path.relative("tmp", filename));
let text = cat(filename),
title;
process.stdout.write(`> Updating files (Steps 4-9): ${i}/${length} - ${filePath + " ".repeat(30)}\r`);
// 5. Prepend page title and layout variables at the top of rules
if (path.dirname(filename).indexOf("rules") >= 0) {
// Find out if the rule requires a special docs portion (e.g. if it is recommended and/or fixable)
const rule = rules.get(ruleName);
const isRecommended = rule && rule.meta.docs.recommended;
const isFixable = rule && rule.meta.fixable;
// Incorporate the special portion into the documentation content
const textSplit = text.split("\n");
const ruleHeading = textSplit[0];
const ruleDocsContent = textSplit.slice(1).join("\n");
text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}\n${ruleDocsContent}`;
title = `${ruleName} - Rules`;
} else {
// extract the title from the file itself
title = text.match(/#([^#].+)\n/);
if (title) {
title = title[1].trim();
} else {
title = "Documentation";
}
}
text = [
"---",
`title: ${title}`,
"layout: doc",
`edit_link: https://github.com/eslint/eslint/edit/master/${filePath}`,
"---",
"<!-- Note: No pull requests accepted for this file. See README.md in the root directory for details. -->",
"",
text
].join("\n");
// 6. Remove .md extension for relative links and change README to empty string
text = text.replace(/\((?!https?:\/\/)([^)]*?)\.md(.*?)\)/g, "($1$2)").replace("README.html", "");
// 7. Check if there's a trailing white line at the end of the file, if there isn't one, add it
if (!/\n$/.test(text)) {
text = `${text}\n`;
}
// 8. Append first version of ESLint rule was added at.
if (filename.indexOf("rules/") !== -1) {
if (!versions.added[baseName]) {
versions.added[baseName] = getFirstVersionOfFile(sourcePath);
}
const added = versions.added[baseName];
if (!versions.removed[baseName] && !test("-f", sourcePath)) {
versions.removed[baseName] = getFirstVersionOfDeletion(sourcePath);
}
const removed = versions.removed[baseName];
text += "\n## Version\n\n";
text += removed
? `This rule was introduced in ESLint ${added} and removed in ${removed}.\n`
: `This rule was introduced in ESLint ${added}.\n`;
text += "\n## Resources\n\n";
if (!removed) {
text += `* [Rule source](${rulesUrl}${sourceBaseName})\n`;
}
text += `* [Documentation source](${docsUrl}${baseName})\n`;
}
// 9. Update content of the file with changes
text.to(filename.replace("README.md", "index.md"));
}
});
JSON.stringify(versions).to("./versions.json");
echo(`> Updating files (Steps 4-9)${" ".repeat(50)}`);
// 10. Copy temporary directory to site's docs folder
echo("> Copying the temporary directory the site (Step 10)");
let outputDir = DOCS_DIR;
if (prereleaseVersion) {
outputDir += `/${prereleaseVersion}`;
if (!test("-d", outputDir)) {
mkdir(outputDir);
}
}
cp("-rf", `${TEMP_DIR}*`, outputDir);
// 11. Generate rule listing page
echo("> Generating the rule listing (Step 11)");
generateRuleIndexPage(process.cwd());
// 12. Delete temporary directory
echo("> Removing the temporary directory (Step 12)");
rm("-rf", TEMP_DIR);
// 13. Update demos, but only for non-prereleases
if (!prereleaseVersion) {
echo("> Updating the demos (Step 13)");
target.browserify();
cp("-f", "build/eslint.js", `${SITE_DIR}js/app/eslint.js`);
} else {
echo("> Skipped updating the demos (Step 13)");
}
// 14. Create Example Formatter Output Page
echo("> Creating the formatter examples (Step 14)");
generateFormatterExamples(getFormatterResults(), prereleaseVersion);
echo("Done generating eslint.org");
};
target.browserify = function() {
// 1. create temp and build directory
if (!test("-d", TEMP_DIR)) {
mkdir(TEMP_DIR);
}
if (!test("-d", BUILD_DIR)) {
mkdir(BUILD_DIR);
}
// 2. copy files into temp directory
cp("-r", "lib/*", TEMP_DIR);
// 3. delete the load-rules.js file
rm("-rf", `${TEMP_DIR}load-rules.js`);
// 4. create new load-rule.js with hardcoded requires
generateRulesIndex(TEMP_DIR);
// 5. browserify the temp directory
exec(`${getBinFile("browserify")} -x espree ${TEMP_DIR}linter.js -o ${BUILD_DIR}eslint.js -s eslint --global-transform [ babelify --presets [ es2015 ] ]`);
// 6. Browserify espree
exec(`${getBinFile("browserify")} -r espree -o ${TEMP_DIR}espree.js`);
// 7. Concatenate Babel polyfill, Espree, and ESLint files together
cat("./node_modules/babel-polyfill/dist/polyfill.js", `${TEMP_DIR}espree.js`, `${BUILD_DIR}eslint.js`).to(`${BUILD_DIR}eslint.js`);
// 8. remove temp directory
rm("-rf", TEMP_DIR);
};
target.checkRuleFiles = function() {
echo("Validating rules");
const eslintRecommended = require("./conf/eslint-recommended").rules;
const ruleFiles = find("lib/rules/").filter(fileType("js"));
let errors = 0;
ruleFiles.forEach(filename => {
const basename = path.basename(filename, ".js");
const docFilename = `docs/rules/${basename}.md`;
/**
* Check if basename is present in eslint:recommended configuration.
* @returns {boolean} true if present
* @private
*/
function isInConfig() {
return Object.prototype.hasOwnProperty.call(eslintRecommended, basename);
}
/**
* Check if id is present in title
* @param {string} id id to check for
* @returns {boolean} true if present
* @private
*/
function hasIdInTitle(id) {
const docText = cat(docFilename);
const idOldAtEndOfTitleRegExp = new RegExp(`^# (.*?) \\(${id}\\)`); // original format
const idNewAtBeginningOfTitleRegExp = new RegExp(`^# ${id}: `); // new format is same as rules index
/*
* 1. Added support for new format.
* 2. Will remove support for old format after all docs files have new format.
* 3. Will remove this check when the main heading is automatically generated from rule metadata.
*/
return idNewAtBeginningOfTitleRegExp.test(docText) || idOldAtEndOfTitleRegExp.test(docText);
}
// check for docs
if (!test("-f", docFilename)) {
console.error("Missing documentation for rule %s", basename);
errors++;
} else {
// check for proper doc format
if (!hasIdInTitle(basename)) {
console.error("Missing id in the doc page's title of rule %s", basename);
errors++;
}
}
// check for recommended configuration
if (!isInConfig()) {
console.error("Missing eslint:recommended setting for %s in conf/eslint-recommended.js", basename);
errors++;
}
// check for tests
if (!test("-f", `tests/lib/rules/${basename}.js`)) {
console.error("Missing tests for rule %s", basename);
errors++;
}
});
if (errors) {
exit(1);
}
};
target.checkLicenses = function() {
/**
* Check if a dependency is eligible to be used by us
* @param {Object} dependency dependency to check
* @returns {boolean} true if we have permission
* @private
*/
function isPermissible(dependency) {
const licenses = dependency.licenses;
if (Array.isArray(licenses)) {
return licenses.some(license => isPermissible({
name: dependency.name,
licenses: license
}));
}
return OPEN_SOURCE_LICENSES.some(license => license.test(licenses));
}
echo("Validating licenses");
checker.init({
start: __dirname
}, deps => {
const impermissible = Object.keys(deps).map(dependency => ({
name: dependency,
licenses: deps[dependency].licenses
})).filter(dependency => !isPermissible(dependency));
if (impermissible.length) {
impermissible.forEach(dependency => {
console.error(
"%s license for %s is impermissible.",
dependency.licenses,
dependency.name
);
});
exit(1);
}
});
};
/**
* Downloads a repository which has many js files to test performance with multi files.
* Here, it's eslint@1.10.3 (450 files)
* @param {Function} cb - A callback function.
* @returns {void}
*/
function downloadMultifilesTestTarget(cb) {
if (test("-d", PERF_MULTIFILES_TARGET_DIR)) {
process.nextTick(cb);
} else {
mkdir("-p", PERF_MULTIFILES_TARGET_DIR);
echo("Downloading the repository of multi-files performance test target.");
exec(`git clone -b v1.10.3 --depth 1 https://github.com/eslint/eslint.git "${PERF_MULTIFILES_TARGET_DIR}"`, { silent: true }, cb);
}
}
/**
* Creates a config file to use performance tests.
* This config is turning all core rules on.
* @returns {void}
*/
function createConfigForPerformanceTest() {
const content = [
"root: true",
"env:",
" node: true",
" es6: true",
"rules:"
];
content.push(...ls("lib/rules").map(fileName => ` ${path.basename(fileName, ".js")}: 1`));
content.join("\n").to(PERF_ESLINTRC);
}
/**
* Calculates the time for each run for performance
* @param {string} cmd cmd
* @param {int} runs Total number of runs to do
* @param {int} runNumber Current run number
* @param {int[]} results Collection results from each run
* @param {Function} cb Function to call when everything is done
* @returns {int[]} calls the cb with all the results
* @private
*/
function time(cmd, runs, runNumber, results, cb) {
const start = process.hrtime();
exec(cmd, { silent: true }, (code, stdout, stderr) => {
const diff = process.hrtime(start),
actual = (diff[0] * 1e3 + diff[1] / 1e6); // ms
if (code) {
echo(` Performance Run #${runNumber} failed.`);
if (stdout) {
echo(`STDOUT:\n${stdout}\n\n`);
}
if (stderr) {
echo(`STDERR:\n${stderr}\n\n`);
}
return cb(null);
}
results.push(actual);
echo(` Performance Run #${runNumber}: %dms`, actual);
if (runs > 1) {
return time(cmd, runs - 1, runNumber + 1, results, cb);
}
return cb(results);
});
}
/**
* Run a performance test.
*
* @param {string} title - A title.
* @param {string} targets - Test targets.
* @param {number} multiplier - A multiplier for limitation.
* @param {Function} cb - A callback function.
* @returns {void}
*/
function runPerformanceTest(title, targets, multiplier, cb) {
const cpuSpeed = os.cpus()[0].speed,
max = multiplier / cpuSpeed,
cmd = `${ESLINT}--config "${PERF_ESLINTRC}" --no-eslintrc --no-ignore ${targets}`;
echo("");
echo(title);
echo(" CPU Speed is %d with multiplier %d", cpuSpeed, multiplier);
time(cmd, 5, 1, [], results => {
if (!results || results.length === 0) { // No results? Something is wrong.
throw new Error("Performance test failed.");
}
results.sort((a, b) => a - b);
const median = results[~~(results.length / 2)];
echo("");
if (median > max) {
echo(" Performance budget exceeded: %dms (limit: %dms)", median, max);
} else {
echo(" Performance budget ok: %dms (limit: %dms)", median, max);
}
echo("");
cb();
});
}
/**
* Run the load performance for eslint
* @returns {void}
* @private
*/
function loadPerformance() {
echo("");
echo("Loading:");
const results = [];
for (let cnt = 0; cnt < 5; cnt++) {
const loadPerfData = loadPerf({
checkDependencies: false
});
echo(` Load performance Run #${cnt + 1}: %dms`, loadPerfData.loadTime);
results.push(loadPerfData.loadTime);
}
results.sort((a, b) => a - b);
const median = results[~~(results.length / 2)];
echo("");
echo(" Load Performance median: %dms", median);
echo("");
}
target.perf = function() {
downloadMultifilesTestTarget(() => {
createConfigForPerformanceTest();
loadPerformance();
runPerformanceTest(
"Single File:",
"tests/performance/jshint.js",
PERF_MULTIPLIER,
() => {
// Count test target files.
const count = glob.sync(
process.platform === "win32"
? PERF_MULTIFILES_TARGETS.slice(2).replace("\\", "/")
: PERF_MULTIFILES_TARGETS
).length;
runPerformanceTest(
`Multi Files (${count} files):`,
PERF_MULTIFILES_TARGETS,
3 * PERF_MULTIPLIER,
() => {}
);
}
);
});
};
target.release = function() {
release();
};
target.ciRelease = function() {
release(true);
};
target.publishsite = function() {
publishSite();
};
target.prerelease = function(args) {
prerelease(args[0]);
};
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/dssljt/eslint.git
git@gitee.com:dssljt/eslint.git
dssljt
eslint
eslint
master

搜索帮助

23e8dbc6 1850385 7e0993f3 1850385