UNPKG

@storm-software/eslint

Version:

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

350 lines (347 loc) 10.7 kB
import { getFileBanner } from './chunk-TBHRJJNJ.js'; import { GLOB_SRC } from './chunk-QQBZQ6FI.js'; import { init_esm_shims } from './chunk-H2OHWXFL.js'; import { ESLintUtils } from '@typescript-eslint/utils'; import os from 'node:os'; // src/utils/banner-plugin.ts init_esm_shims(); 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.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 = 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; export { banner_plugin_default };