generateRule.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. "use strict";
  2. var _camelcase = _interopRequireDefault(require("camelcase"));
  3. var _fs = require("fs");
  4. var _promises = _interopRequireDefault(require("fs/promises"));
  5. var _openEditor = _interopRequireDefault(require("open-editor"));
  6. var _path = require("path");
  7. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  8. function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
  9. function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /* eslint-disable no-console -- CLI */ /**
  10. * @example
  11. *
  12. * ```shell
  13. * npm run create-rule my-new-rule -- --recommended
  14. * ```
  15. */
  16. // Todo: Would ideally have prompts, e.g., to ask for whether
  17. // type was problem/layout, etc.
  18. const [,, ruleName, ...options] = process.argv;
  19. const recommended = options.includes('--recommended');
  20. (async () => {
  21. if (!ruleName) {
  22. console.error('Please supply a rule name');
  23. return;
  24. }
  25. if (/[A-Z]/u.test(ruleName)) {
  26. console.error('Please ensure the rule has no capital letters');
  27. return;
  28. }
  29. const ruleNamesPath = './test/rules/ruleNames.json';
  30. // @ts-expect-error Older types?
  31. const ruleNames = JSON.parse(await _promises.default.readFile(ruleNamesPath));
  32. if (!ruleNames.includes(ruleName)) {
  33. ruleNames.push(ruleName);
  34. ruleNames.sort();
  35. }
  36. await _promises.default.writeFile(ruleNamesPath, JSON.stringify(ruleNames, null, 2) + '\n');
  37. console.log('ruleNames', ruleNames);
  38. const ruleTemplate = `import iterateJsdoc from '../iterateJsdoc.js';
  39. export default iterateJsdoc(({
  40. context,
  41. utils,
  42. }) => {
  43. // Rule here
  44. }, {
  45. iterateAllJsdocs: true,
  46. meta: {
  47. docs: {
  48. description: '',
  49. url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/${ruleName}.md#repos-sticky-header',
  50. },
  51. schema: [
  52. {
  53. additionalProperties: false,
  54. properties: {
  55. // Option properties here (or remove the object)
  56. },
  57. type: 'object',
  58. },
  59. ],
  60. type: 'suggestion',
  61. },
  62. });
  63. `;
  64. const camelCasedRuleName = (0, _camelcase.default)(ruleName);
  65. const rulePath = `./src/rules/${camelCasedRuleName}.js`;
  66. if (!(0, _fs.existsSync)(rulePath)) {
  67. await _promises.default.writeFile(rulePath, ruleTemplate);
  68. }
  69. const ruleTestTemplate = `export default {
  70. invalid: [
  71. {
  72. code: \`
  73. \`,
  74. errors: [
  75. {
  76. line: 2,
  77. message: '',
  78. },
  79. ],
  80. },
  81. ],
  82. valid: [
  83. {
  84. code: \`
  85. \`,
  86. },
  87. ],
  88. };
  89. `;
  90. const ruleTestPath = `./test/rules/assertions/${camelCasedRuleName}.js`;
  91. if (!(0, _fs.existsSync)(ruleTestPath)) {
  92. await _promises.default.writeFile(ruleTestPath, ruleTestTemplate);
  93. }
  94. const ruleReadmeTemplate = `### \`${ruleName}\`
  95. |||
  96. |---|---|
  97. |Context|everywhere|
  98. |Tags|\`\`|
  99. |Recommended|${recommended ? 'true' : 'false'}|
  100. |Settings||
  101. |Options||
  102. ## Failing examples
  103. <!-- assertions-failing ${camelCasedRuleName} -->
  104. ## Passing examples
  105. <!-- assertions-passing ${camelCasedRuleName} -->
  106. `;
  107. const ruleReadmePath = `./.README/rules/${ruleName}.md`;
  108. if (!(0, _fs.existsSync)(ruleReadmePath)) {
  109. await _promises.default.writeFile(ruleReadmePath, ruleReadmeTemplate);
  110. }
  111. /**
  112. * @param {object} cfg
  113. * @param {string} cfg.path
  114. * @param {RegExp} cfg.oldRegex
  115. * @param {string} cfg.checkName
  116. * @param {string} cfg.newLine
  117. * @param {boolean} [cfg.oldIsCamel]
  118. * @returns {Promise<void>}
  119. */
  120. const replaceInOrder = async ({
  121. path,
  122. oldRegex,
  123. checkName,
  124. newLine,
  125. oldIsCamel
  126. }) => {
  127. /**
  128. * @typedef {number} Integer
  129. */
  130. /**
  131. * @typedef {{
  132. * matchedLine: string,
  133. * offset: Integer,
  134. * oldRule: string,
  135. * }} OffsetInfo
  136. */
  137. /**
  138. * @type {OffsetInfo[]}
  139. */
  140. const offsets = [];
  141. let readme = await _promises.default.readFile(path, 'utf8');
  142. readme.replace(oldRegex,
  143. /**
  144. * @param {string} matchedLine
  145. * @param {string} n1
  146. * @param {Integer} offset
  147. * @param {string} str
  148. * @param {object} groups
  149. * @param {string} groups.oldRule
  150. * @returns {string}
  151. */
  152. (matchedLine, n1, offset, str, {
  153. oldRule
  154. }) => {
  155. offsets.push({
  156. matchedLine,
  157. offset,
  158. oldRule
  159. });
  160. return matchedLine;
  161. });
  162. offsets.sort(({
  163. oldRule
  164. }, {
  165. oldRule: oldRuleB
  166. }) => {
  167. return oldRule < oldRuleB ? -1 : oldRule > oldRuleB ? 1 : 0;
  168. });
  169. let alreadyIncluded = false;
  170. const itemIndex = offsets.findIndex(({
  171. oldRule
  172. }) => {
  173. alreadyIncluded ||= oldIsCamel ? camelCasedRuleName === oldRule : ruleName === oldRule;
  174. return oldIsCamel ? camelCasedRuleName < oldRule : ruleName < oldRule;
  175. });
  176. let item = itemIndex !== undefined && offsets[itemIndex];
  177. if (item && itemIndex === 0 &&
  178. // This property would not always be sufficient but in this case it is.
  179. oldIsCamel) {
  180. item.offset = 0;
  181. }
  182. if (!item) {
  183. item = /** @type {OffsetInfo} */offsets.pop();
  184. item.offset += item.matchedLine.length;
  185. }
  186. if (alreadyIncluded) {
  187. console.log(`Rule name is already present in ${checkName}.`);
  188. } else {
  189. readme = readme.slice(0, item.offset) + (item.offset ? '\n' : '') + newLine + (item.offset ? '' : '\n') + readme.slice(item.offset);
  190. await _promises.default.writeFile(path, readme);
  191. }
  192. };
  193. // await replaceInOrder({
  194. // checkName: 'README',
  195. // newLine: `{"gitdown": "include", "file": "./rules/${ruleName}.md"}`,
  196. // oldRegex: /\n\{"gitdown": "include", "file": ".\/rules\/(?<oldRule>[^.]*).md"\}/gu,
  197. // path: './.README/README.md',
  198. // });
  199. await replaceInOrder({
  200. checkName: 'index import',
  201. newLine: `import ${camelCasedRuleName} from './rules/${camelCasedRuleName}.js';`,
  202. oldIsCamel: true,
  203. oldRegex: /\nimport (?<oldRule>[^ ]*) from '.\/rules\/\1\.js';/gu,
  204. path: './src/index.js'
  205. });
  206. await replaceInOrder({
  207. checkName: 'index recommended',
  208. newLine: `${' '.repeat(6)}'jsdoc/${ruleName}': ${recommended ? 'warnOrError' : '\'off\''},`,
  209. oldRegex: /\n\s{6}'jsdoc\/(?<oldRule>[^']*)': .*?,/gu,
  210. path: './src/index.js'
  211. });
  212. await replaceInOrder({
  213. checkName: 'index rules',
  214. newLine: `${' '.repeat(4)}'${ruleName}': ${camelCasedRuleName},`,
  215. oldRegex: /\n\s{4}'(?<oldRule>[^']*)': [^,]*,/gu,
  216. path: './src/index.js'
  217. });
  218. await Promise.resolve().then(() => _interopRequireWildcard(require('./generateDocs.js')));
  219. /*
  220. console.log('Paths to open for further editing\n');
  221. console.log(`open ${ruleReadmePath}`);
  222. console.log(`open ${rulePath}`);
  223. console.log(`open ${ruleTestPath}\n`);
  224. */
  225. // Set chdir as somehow still in operation from other test
  226. process.chdir((0, _path.resolve)(__dirname, '../../'));
  227. await (0, _openEditor.default)([
  228. // Could even add editor line column numbers like `${rulePath}:1:1`
  229. ruleReadmePath, ruleTestPath, rulePath]);
  230. })();
  231. //# sourceMappingURL=generateRule.js.map