UNPKG

@storm-software/eslint

Version:

A package containing the base ESLint configuration used by Storm Software across many projects.

459 lines (444 loc) 14.7 kB
'use strict'; var utils = require('@typescript-eslint/utils'); var os = require('os'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var os__default = /*#__PURE__*/_interopDefault(os); // src/utils/banner-plugin.ts // src/utils/constants.ts var ACRONYMS_LIST = [ "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", "OEM", "PP", "QA", "RAM", "RHS", "RPC", "RSS", "SLA", "SMTP", "SQL", "SSH", "SSL", "TCP", "TLS", "TTL", "UDP", "UI", "UID", "UUID", "URI", "URL", "UTF", "VM", "XML", "XSS" ]; var GLOB_SRC_EXT = "?([cm])[jt]s?(x)"; var GLOB_SRC_FILE = `*.${GLOB_SRC_EXT}`; var GLOB_SRC = `**/${GLOB_SRC_FILE}`; // src/utils/get-file-banner.ts var getFileBanner = (name = "", workspaceConfig) => { if (!name) { name = process.env.STORM_NAME || ""; } let padding = " "; for (let i = 0; i < name.length + 2 && padding.length > 4; i++) { padding = padding.slice(0, -1); } let titleName = name || workspaceConfig?.name; if (titleName) { if (titleName?.startsWith("@")) { titleName = titleName.slice(1); } titleName = (titleName.charAt(0).toUpperCase() + titleName.slice(1)).split("-").filter((word) => word && word.length > 0).map((word) => { if (ACRONYMS_LIST.includes(word.toUpperCase())) { return word.toUpperCase(); } return word.charAt(0).toUpperCase() + word.slice(1); }).join(" "); } const license = (process.env.STORM_LICENSE || workspaceConfig?.license || "Apache-2.0").split(" ").filter((word) => word && word.toLowerCase() !== "license").join(" "); const organization = process.env.STORM_ORG_NAME || process.env.STORM_ORGANIZATION_NAME || process.env.STORM_ORG || process.env.STORM_ORGANIZATION || (void 0) || "storm-software"; return ` ------------------------------------------------------------------- ${padding}\u26A1 ${(organization.charAt(0).toUpperCase() + organization.slice(1)).split("-").filter((word) => word && word.length > 0).map((word) => { if (ACRONYMS_LIST.includes(word.toUpperCase())) { return word.toUpperCase(); } return word.charAt(0).toUpperCase() + word.slice(1); }).join(" ")} ${titleName ? `- ${titleName}` : ""} This code was released as part of ${titleName ? `the ${titleName}` : `a ${(organization.charAt(0).toUpperCase() + organization.slice(1)).split("-").filter((word) => word && word.length > 0).map((word) => { if (ACRONYMS_LIST.includes(word.toUpperCase())) { return word.toUpperCase(); } return word.charAt(0).toUpperCase() + word.slice(1); }).join(" ")}`} project. ${titleName ? titleName : "The project"} is maintained by ${(organization.charAt(0).toUpperCase() + organization.slice(1)).split("-").filter((word) => word && word.length > 0).map((word) => { if (ACRONYMS_LIST.includes(word.toUpperCase())) { return word.toUpperCase(); } return word.charAt(0).toUpperCase() + word.slice(1); }).join(" ")} under the ${license} license, and is free for commercial and private use. For more information, please visit our licensing page at ${process.env.STORM_LICENSING?.replace(/\/$/, "") || workspaceConfig?.licensing?.replace(/\/$/, "") || "https://stormsoftware.com/licenses"}/${name ? `projects/${name}` : ""}. Website: ${process.env.STORM_HOMEPAGE || workspaceConfig?.homepage || "https://stormsoftware.com"} Repository: ${process.env.STORM_REPOSITORY || workspaceConfig?.repository || `https://github.com/${organization}${name ? `/${name}` : ""}`} Documentation: ${process.env.STORM_DOCS || workspaceConfig?.docs || `https://docs.stormsoftware.com${name ? `/projects/${name}` : ""}`} Contact: ${(process.env.STORM_HOMEPAGE || workspaceConfig?.homepage || "https://stormsoftware.com").endsWith("/") ? (process.env.STORM_HOMEPAGE || workspaceConfig?.homepage || "https://stormsoftware.com").slice(-1) : process.env.STORM_HOMEPAGE || workspaceConfig?.homepage || "https://stormsoftware.com"}/contact SPDX-License-Identifier: ${license} ------------------------------------------------------------------- `; }; // src/utils/banner-plugin.ts function match(actual, expected) { if (expected.test) { return expected.test(actual); } else { return expected === actual; } } function excludeShebangs(comments) { return comments.filter((comment) => { return comment.type !== "Shebang"; }); } function getLeadingComments(context, node) { const all = excludeShebangs( context.getSourceCode().getAllComments(node.body.length ? node.body[0] : node) ); if (all[0].type.toLowerCase() === "block") { return [all[0]]; } let i = 1; for (i = 1; i < all.length; ++i) { const txt = context.getSourceCode().getText().slice(all[i - 1].range[1], all[i].range[0]); if (!txt.match(/^(\r\n|\r|\n)$/)) { break; } } return all.slice(0, i); } function genCommentBody(commentType, textArray, eol, numNewlines) { const eols = eol.repeat(numNewlines); if (commentType === "block") { return "/*" + textArray.join(eol) + "*/" + eols; } else { return "//" + textArray.join(eol + "//") + eols; } } function genCommentsRange(context, comments, eol) { const start = comments[0].range[0]; let end = comments.slice(-1)[0].range[1]; if (context.getSourceCode().text[end] === eol) { end += eol.length; } return [start, end]; } function genPrependFixer(commentType, node, bannerLines, eol, numNewlines) { return function(fixer) { return fixer.insertTextBefore( node, genCommentBody(commentType, bannerLines, eol, numNewlines) ); }; } function genReplaceFixer(commentType, context, leadingComments, bannerLines, eol, numNewlines) { return function(fixer) { return fixer.replaceTextRange( genCommentsRange(context, leadingComments, eol), genCommentBody(commentType, bannerLines, eol, numNewlines) ); }; } function getEOL(options) { if (options.lineEndings === "unix") { return "\n"; } if (options.lineEndings === "windows") { return "\r\n"; } return os__default.default.EOL; } function hasBanner(commentType = "block", src, eol) { if (src.startsWith("#!")) { const bannerLines = src.split(eol); if (bannerLines && bannerLines.length > 1) { bannerLines.shift(); while (bannerLines.length && bannerLines[0] && !bannerLines[0].trim()) { bannerLines.shift(); } if (bannerLines.length) { src = bannerLines.join(eol); } else { return false; } } } return commentType === "block" && src.startsWith("/*") || commentType === "line" && src.startsWith("//") || commentType !== "block" && commentType !== "line" && commentType && src.startsWith(commentType); } function matchesLineEndings(src, num) { for (let j = 0; j < num; ++j) { const m = src.match(/^(\r\n|\r|\n)/); if (m) { src = src.slice(m.index + m[0].length); } else { return false; } } return true; } var bannerRule = utils.ESLintUtils.RuleCreator( () => `https://developer.stormsoftware.com/eslint/rules/banner` )({ name: "banner", meta: { docs: { description: "Ensures the file has a organization specific banner at the top of source code files" }, schema: [ { type: "object", properties: { banner: { type: "string", description: "The banner to enforce at the top of the file. If not provided, the banner will be read from the file specified in the commentStart option" }, name: { type: "string", description: "The name of the repository to use when reading the banner from a file." }, commentType: { type: "string", description: "The comment token to use for the banner. Defaults to block ('/* <banner> */')" }, numNewlines: { type: "number", description: "The number of newlines to use after the banner. Defaults to 1" }, lineEndings: { type: "string", enum: ["unix", "windows"], description: "The type of line endings to use. Defaults to the system default" } }, additionalProperties: false } ], type: "layout", messages: { missingBanner: "Missing banner", incorrectComment: "Banner should use the {{commentType}} comment type", incorrectBanner: "Incorrect banner", noNewlineAfterBanner: "No newline after banner" }, fixable: "whitespace" }, defaultOptions: [ { name: "", commentType: "block", numNewlines: 2, lineEndings: "unix" } ], create(context, [ { banner, name = "", commentType = "block", numNewlines = 2, lineEndings = "unix" } ]) { if (!banner) { banner = getFileBanner(name); } const options = context.options; const eol = getEOL({ lineEndings, ...options }); const bannerLines = banner.split(/\r?\n/); let fixLines = bannerLines; return { Program: function(node) { if (!hasBanner(commentType, context.sourceCode.getText(), eol)) { context.report({ loc: node.loc, messageId: "missingBanner", fix: genPrependFixer(commentType, node, fixLines, eol, numNewlines) }); } else { const leadingComments = getLeadingComments(context, node); if (!leadingComments.length) { context.report({ loc: node.loc, messageId: "missingBanner", fix: genPrependFixer(commentType, node, fixLines, eol, numNewlines) }); } else if (leadingComments[0].type.toLowerCase() !== commentType) { context.report({ loc: node.loc, messageId: "incorrectComment", data: { commentType }, fix: genReplaceFixer( commentType, context, leadingComments, fixLines, eol, numNewlines ) }); } else { if (commentType === "line") { if (leadingComments.length < bannerLines.length) { context.report({ loc: node.loc, messageId: "missingBanner", fix: genReplaceFixer( commentType, context, leadingComments, fixLines, eol, numNewlines ) }); return; } for (let i = 0; i < bannerLines.length; i++) { if (!match(leadingComments[i].value, bannerLines[i])) { context.report({ loc: node.loc, messageId: "incorrectBanner", fix: genReplaceFixer( commentType, context, leadingComments, fixLines, eol, numNewlines ) }); return; } } const postLineBanner = context.getSourceCode().text.substr( leadingComments[bannerLines.length - 1].range[1], (numNewlines ?? 1) * 2 ); if (!matchesLineEndings(postLineBanner, numNewlines)) { context.report({ loc: node.loc, messageId: "noNewlineAfterBanner", fix: genReplaceFixer( commentType, context, leadingComments, fixLines, eol, numNewlines ) }); } } else { let leadingLines = [leadingComments[0].value]; if (bannerLines.length > 1) { leadingLines = leadingComments[0].value.split(/\r?\n/); } let hasError = false; if (leadingLines.length > bannerLines.length) { hasError = true; } for (let i = 0; !hasError && i < bannerLines.length; i++) { if (!match(leadingLines[i], bannerLines[i])) { hasError = true; } } if (hasError) { if (bannerLines.length > 1) { fixLines = [fixLines.join(eol)]; } context.report({ loc: node.loc, messageId: "incorrectBanner", fix: genReplaceFixer( commentType, context, leadingComments, fixLines, eol, numNewlines ) }); } else { const postBlockBanner = context.getSourceCode().text.substr( leadingComments[0].range[1], (numNewlines ?? 1) * 2 ); if (!matchesLineEndings(postBlockBanner, numNewlines)) { context.report({ loc: node.loc, messageId: "noNewlineAfterBanner", fix: genReplaceFixer( commentType, context, leadingComments, fixLines, eol, numNewlines ) }); } } } } } } }; } }); var plugin = { meta: { name: "eslint-plugin-banner", version: "0.0.1" }, configs: {}, rules: { banner: bannerRule }, processors: {} }; plugin.configs && (plugin.configs.recommended = { name: "banner/recommended", plugins: { banner: plugin }, files: [GLOB_SRC], ignores: [ "!**/docs/**/*", "!**/crates/**/*", "!**/tmp/**/*", "!**/dist/**/*", "!**/coverage/**/*", "!**/node_modules/**/*", "!**/.cache/**/*", "!**/.nx/**/*", "!**/.storm/**/*" ], rules: { "banner/banner": ["error", { commentType: "block", numNewlines: 2 }] } }); var banner_plugin_default = plugin; module.exports = banner_plugin_default; module.exports = exports.default;