| "use strict"; |
| |
| var _camelcase = _interopRequireDefault(require("camelcase")); |
| var _nodeFs = require("node:fs"); |
| var _promises = _interopRequireDefault(require("node:fs/promises")); |
| var _nodePath = require("node:path"); |
| var _openEditor = _interopRequireDefault(require("open-editor")); |
| function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } |
| function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /* eslint-disable no-console -- CLI */ /** |
| * @example |
| * ```shell |
| * pnpm run create-rule my-new-rule -- --recommended |
| * ``` |
| */ |
| // Todo: Would ideally have prompts, e.g., to ask for whether |
| // type was problem/layout, etc. |
| |
| const [,, ruleName, ...options] = process.argv; |
| const recommended = options.includes('--recommended'); |
| (async () => { |
| if (!ruleName) { |
| console.error('Please supply a rule name'); |
| return; |
| } |
| if (/[A-Z]/v.test(ruleName)) { |
| console.error('Please ensure the rule has no capital letters'); |
| return; |
| } |
| const ruleNamesPath = './test/rules/ruleNames.json'; |
| const ruleNames = JSON.parse(await _promises.default.readFile( |
| // eslint-disable-next-line unicorn/prefer-json-parse-buffer -- TypeScript |
| ruleNamesPath, 'utf8')); |
| if (!ruleNames.includes(ruleName)) { |
| ruleNames.push(ruleName); |
| ruleNames.sort(); |
| } |
| await _promises.default.writeFile(ruleNamesPath, JSON.stringify(ruleNames, null, 2) + '\n'); |
| console.log('ruleNames', ruleNames); |
| const ruleTemplate = `import iterateJsdoc from '../iterateJsdoc.js'; |
| |
| export default iterateJsdoc(({ |
| context, |
| utils, |
| }) => { |
| // Rule here |
| }, { |
| iterateAllJsdocs: true, |
| meta: { |
| docs: { |
| description: '', |
| url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/${ruleName}.md#repos-sticky-header', |
| }, |
| schema: [ |
| { |
| additionalProperties: false, |
| properties: { |
| // Option properties here (or remove the object) |
| }, |
| type: 'object', |
| }, |
| ], |
| type: 'suggestion', |
| }, |
| }); |
| `; |
| const camelCasedRuleName = (0, _camelcase.default)(ruleName); |
| const rulePath = `./src/rules/${camelCasedRuleName}.js`; |
| if (!(0, _nodeFs.existsSync)(rulePath)) { |
| await _promises.default.writeFile(rulePath, ruleTemplate); |
| } |
| const ruleTestTemplate = `export default { |
| invalid: [ |
| { |
| code: \` |
| \`, |
| errors: [ |
| { |
| line: 2, |
| message: '', |
| }, |
| ], |
| }, |
| ], |
| valid: [ |
| { |
| code: \` |
| \`, |
| }, |
| ], |
| }; |
| `; |
| const ruleTestPath = `./test/rules/assertions/${camelCasedRuleName}.js`; |
| if (!(0, _nodeFs.existsSync)(ruleTestPath)) { |
| await _promises.default.writeFile(ruleTestPath, ruleTestTemplate); |
| } |
| const ruleReadmeTemplate = `# \`${ruleName}\` |
| |
| ## Options |
| |
| {"gitdown": "options"} |
| |
| ||| |
| |---|---| |
| |Context|everywhere| |
| |Tags|\`\`| |
| |Recommended|${recommended ? 'true' : 'false'}| |
| |Settings|| |
| |Options|| |
| |
| ## Failing examples |
| |
| <!-- assertions-failing ${camelCasedRuleName} --> |
| |
| ## Passing examples |
| |
| <!-- assertions-passing ${camelCasedRuleName} --> |
| `; |
| const ruleReadmePath = `./.README/rules/${ruleName}.md`; |
| if (!(0, _nodeFs.existsSync)(ruleReadmePath)) { |
| await _promises.default.writeFile(ruleReadmePath, ruleReadmeTemplate); |
| } |
| |
| /** |
| * @param {object} cfg |
| * @param {string} cfg.path |
| * @param {RegExp} cfg.oldRegex |
| * @param {string} cfg.checkName |
| * @param {string} cfg.newLine |
| * @param {boolean} [cfg.oldIsCamel] |
| * @returns {Promise<void>} |
| */ |
| const replaceInOrder = async ({ |
| checkName, |
| newLine, |
| oldIsCamel, |
| oldRegex, |
| path |
| }) => { |
| /** |
| * @typedef {number} Integer |
| */ |
| /** |
| * @typedef {{ |
| * matchedLine: string, |
| * offset: Integer, |
| * oldRule: string, |
| * }} OffsetInfo |
| */ |
| /** |
| * @type {OffsetInfo[]} |
| */ |
| const offsets = []; |
| let readme = await _promises.default.readFile(path, 'utf8'); |
| readme.replace(oldRegex, |
| /** |
| * @param {string} matchedLine |
| * @param {string} n1 |
| * @param {Integer} offset |
| * @param {string} str |
| * @param {object} groups |
| * @param {string} groups.oldRule |
| * @returns {string} |
| */ |
| (matchedLine, n1, offset, str, { |
| oldRule |
| }) => { |
| offsets.push({ |
| matchedLine, |
| offset, |
| oldRule |
| }); |
| return matchedLine; |
| }); |
| offsets.sort(({ |
| oldRule |
| }, { |
| oldRule: oldRuleB |
| }) => { |
| return oldRule < oldRuleB ? -1 : oldRule > oldRuleB ? 1 : 0; |
| }); |
| let alreadyIncluded = false; |
| const itemIndex = offsets.findIndex(({ |
| oldRule |
| }) => { |
| alreadyIncluded ||= oldIsCamel ? camelCasedRuleName === oldRule : ruleName === oldRule; |
| return oldIsCamel ? camelCasedRuleName < oldRule : ruleName < oldRule; |
| }); |
| let item = itemIndex !== undefined && offsets[itemIndex]; |
| if (item && itemIndex === 0 && |
| // This property would not always be sufficient but in this case it is. |
| oldIsCamel) { |
| item.offset = 0; |
| } |
| if (!item) { |
| item = /** @type {OffsetInfo} */offsets.pop(); |
| item.offset += item.matchedLine.length; |
| } |
| if (alreadyIncluded) { |
| console.log(`Rule name is already present in ${checkName}.`); |
| } else { |
| readme = readme.slice(0, item.offset) + (item.offset ? '\n' : '') + newLine + (item.offset ? '' : '\n') + readme.slice(item.offset); |
| await _promises.default.writeFile(path, readme); |
| } |
| }; |
| |
| // await replaceInOrder({ |
| // checkName: 'README', |
| // newLine: `{"gitdown": "include", "file": "./rules/${ruleName}.md"}`, |
| // oldRegex: /\n\{"gitdown": "include", "file": ".\/rules\/(?<oldRule>[^.]*).md"\}/gv, |
| // path: './.README/README.md', |
| // }); |
| |
| await replaceInOrder({ |
| checkName: 'index import', |
| newLine: `import ${camelCasedRuleName} from './rules/${camelCasedRuleName}.js';`, |
| oldIsCamel: true, |
| oldRegex: /\nimport (?<oldRule>[^ ]*) from '.\/rules\/\1\.js';/gv, |
| path: './src/index-cjs.js' |
| }); |
| await replaceInOrder({ |
| checkName: 'index recommended', |
| newLine: `${' '.repeat(6)}'jsdoc/${ruleName}': ${recommended ? 'warnOrError' : '\'off\''},`, |
| oldRegex: /\n\s{6}'jsdoc\/(?<oldRule>[^']*)': .*?,/gv, |
| path: './src/index-cjs.js' |
| }); |
| await replaceInOrder({ |
| checkName: 'index rules', |
| newLine: `${' '.repeat(2)}'${ruleName}': ${camelCasedRuleName},`, |
| oldRegex: /\n\s{2}'(?<oldRule>[^']*)': [^,]*,/gv, |
| path: './src/index-cjs.js' |
| }); |
| await Promise.resolve().then(() => _interopRequireWildcard(require('./buildEntryFileForTS.js'))); |
| await Promise.resolve().then(() => _interopRequireWildcard(require('./generateDocs.js'))); |
| |
| /* |
| console.log('Paths to open for further editing\n'); |
| console.log(`open ${ruleReadmePath}`); |
| console.log(`open ${rulePath}`); |
| console.log(`open ${ruleTestPath}\n`); |
| */ |
| |
| // Set chdir as somehow still in operation from other test |
| process.chdir((0, _nodePath.resolve)(__dirname, '../../')); |
| await (0, _openEditor.default)([ |
| // Could even add editor line column numbers like `${rulePath}:1:1` |
| ruleReadmePath, ruleTestPath, rulePath]); |
| })(); |
| //# sourceMappingURL=generateRule.cjs.map |