check-restricted.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. /**
  2. * @author Toru Nagashima
  3. * See LICENSE file in root directory for full license.
  4. */
  5. "use strict"
  6. const path = require("path")
  7. const { Minimatch } = require("minimatch")
  8. /** @typedef {import("../util/import-target")} ImportTarget */
  9. /**
  10. * @typedef {Object} DefinitionData
  11. * @property {string | string[]} name The name to disallow.
  12. * @property {string} [message] The custom message to show.
  13. */
  14. /**
  15. * Check if matched or not.
  16. * @param {InstanceType<Minimatch>} matcher The matcher.
  17. * @param {boolean} absolute The flag that the matcher is for absolute paths.
  18. * @param {ImportTarget} importee The importee information.
  19. */
  20. function match(matcher, absolute, { filePath, name }) {
  21. if (absolute) {
  22. return filePath != null && matcher.match(filePath)
  23. }
  24. return matcher.match(name)
  25. }
  26. /** Restriction. */
  27. class Restriction {
  28. /**
  29. * Initialize this restriction.
  30. * @param {DefinitionData} def The definition of a restriction.
  31. */
  32. constructor({ name, message }) {
  33. const names = Array.isArray(name) ? name : [name]
  34. const matchers = names.map(raw => {
  35. const negate = raw[0] === "!" && raw[1] !== "("
  36. const pattern = negate ? raw.slice(1) : raw
  37. const absolute = path.isAbsolute(pattern)
  38. const matcher = new Minimatch(pattern, { dot: true })
  39. return { absolute, matcher, negate }
  40. })
  41. this.matchers = matchers
  42. this.message = message ? ` ${message}` : ""
  43. }
  44. /**
  45. * Check if a given importee is disallowed.
  46. * @param {ImportTarget} importee The importee to check.
  47. * @returns {boolean} `true` if the importee is disallowed.
  48. */
  49. match(importee) {
  50. return this.matchers.reduce(
  51. (ret, { absolute, matcher, negate }) =>
  52. negate
  53. ? ret && !match(matcher, absolute, importee)
  54. : ret || match(matcher, absolute, importee),
  55. false
  56. )
  57. }
  58. }
  59. /**
  60. * Create a restriction.
  61. * @param {string | DefinitionData} def A definition.
  62. * @returns {Restriction} Created restriction.
  63. */
  64. function createRestriction(def) {
  65. if (typeof def === "string") {
  66. return new Restriction({ name: def })
  67. }
  68. return new Restriction(def)
  69. }
  70. /**
  71. * Create restrictions.
  72. * @param {(string | DefinitionData | GlobDefinition)[]} defs Definitions.
  73. * @returns {(Restriction | GlobRestriction)[]} Created restrictions.
  74. */
  75. function createRestrictions(defs) {
  76. return (defs || []).map(createRestriction)
  77. }
  78. /**
  79. * Checks if given importees are disallowed or not.
  80. * @param {RuleContext} context - A context to report.
  81. * @param {ImportTarget[]} targets - A list of target information to check.
  82. * @returns {void}
  83. */
  84. module.exports = function checkForRestriction(context, targets) {
  85. const restrictions = createRestrictions(context.options[0])
  86. for (const target of targets) {
  87. const restriction = restrictions.find(r => r.match(target))
  88. if (restriction) {
  89. context.report({
  90. node: target.node,
  91. messageId: "restricted",
  92. data: {
  93. name: target.name,
  94. customMessage: restriction.message,
  95. },
  96. })
  97. }
  98. }
  99. }