rule.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. 'use strict';
  2. const path = require('node:path');
  3. const fs = require('node:fs');
  4. const getDocumentationUrl = require('./get-documentation-url.js');
  5. const isIterable = object => typeof object?.[Symbol.iterator] === 'function';
  6. class FixAbortError extends Error {}
  7. const fixOptions = {
  8. abort() {
  9. throw new FixAbortError('Fix aborted.');
  10. },
  11. };
  12. function wrapFixFunction(fix) {
  13. return fixer => {
  14. const result = fix(fixer, fixOptions);
  15. if (isIterable(result)) {
  16. try {
  17. return [...result];
  18. } catch (error) {
  19. if (error instanceof FixAbortError) {
  20. return;
  21. }
  22. /* c8 ignore next */
  23. throw error;
  24. }
  25. }
  26. return result;
  27. };
  28. }
  29. function reportListenerProblems(listener, context) {
  30. // Listener arguments can be `codePath, node` or `node`
  31. return function (...listenerArguments) {
  32. let problems = listener(...listenerArguments);
  33. if (!problems) {
  34. return;
  35. }
  36. if (!isIterable(problems)) {
  37. problems = [problems];
  38. }
  39. for (const problem of problems) {
  40. if (problem.fix) {
  41. problem.fix = wrapFixFunction(problem.fix);
  42. }
  43. if (Array.isArray(problem.suggest)) {
  44. for (const suggest of problem.suggest) {
  45. if (suggest.fix) {
  46. suggest.fix = wrapFixFunction(suggest.fix);
  47. }
  48. suggest.data = {
  49. ...problem.data,
  50. ...suggest.data,
  51. };
  52. }
  53. }
  54. context.report(problem);
  55. }
  56. };
  57. }
  58. // `checkVueTemplate` function will wrap `create` function, there is no need to wrap twice
  59. const wrappedFunctions = new Set();
  60. function reportProblems(create) {
  61. if (wrappedFunctions.has(create)) {
  62. return create;
  63. }
  64. const wrapped = context => {
  65. const listeners = create(context);
  66. if (!listeners) {
  67. return {};
  68. }
  69. return Object.fromEntries(
  70. Object.entries(listeners)
  71. .map(([selector, listener]) => [selector, reportListenerProblems(listener, context)]),
  72. );
  73. };
  74. wrappedFunctions.add(wrapped);
  75. return wrapped;
  76. }
  77. function checkVueTemplate(create, options) {
  78. const {
  79. visitScriptBlock,
  80. } = {
  81. visitScriptBlock: true,
  82. ...options,
  83. };
  84. create = reportProblems(create);
  85. const wrapped = context => {
  86. const listeners = create(context);
  87. // `vue-eslint-parser`
  88. if (context.parserServices?.defineTemplateBodyVisitor) {
  89. return visitScriptBlock
  90. ? context.parserServices.defineTemplateBodyVisitor(listeners, listeners)
  91. : context.parserServices.defineTemplateBodyVisitor(listeners);
  92. }
  93. return listeners;
  94. };
  95. wrappedFunctions.add(wrapped);
  96. return wrapped;
  97. }
  98. /** @returns {import('eslint').Rule.RuleModule} */
  99. function loadRule(ruleId) {
  100. const rule = require(`../${ruleId}`);
  101. return {
  102. meta: {
  103. // If there is are, options add `[]` so ESLint can validate that no data is passed to the rule.
  104. // https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-schema.md
  105. schema: [],
  106. ...rule.meta,
  107. docs: {
  108. ...rule.meta.docs,
  109. url: getDocumentationUrl(ruleId),
  110. },
  111. },
  112. create: reportProblems(rule.create),
  113. };
  114. }
  115. function loadRules() {
  116. return Object.fromEntries(
  117. fs.readdirSync(path.join(__dirname, '..'), {withFileTypes: true})
  118. .filter(file => file.isFile())
  119. .map(file => {
  120. const ruleId = path.basename(file.name, '.js');
  121. return [ruleId, loadRule(ruleId)];
  122. }),
  123. );
  124. }
  125. module.exports = {
  126. loadRule,
  127. loadRules,
  128. checkVueTemplate,
  129. };