jsdocUtils.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _getDefaultTagStructureForMode = _interopRequireDefault(require("./getDefaultTagStructureForMode.js"));
  7. var _tagNames = require("./tagNames.js");
  8. var _hasReturnValue = require("./utils/hasReturnValue.js");
  9. var _WarnSettings = _interopRequireDefault(require("./WarnSettings.js"));
  10. var _jsdoccomment = require("@es-joy/jsdoccomment");
  11. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  12. /**
  13. * @typedef {number} Integer
  14. */
  15. /**
  16. * @typedef {import('./utils/hasReturnValue.js').ESTreeOrTypeScriptNode} ESTreeOrTypeScriptNode
  17. */
  18. /**
  19. * @typedef {"jsdoc"|"typescript"|"closure"|"permissive"} ParserMode
  20. */
  21. /**
  22. * @type {import('./getDefaultTagStructureForMode.js').TagStructure}
  23. */
  24. let tagStructure;
  25. /**
  26. * @param {ParserMode} mode
  27. * @returns {void}
  28. */
  29. const setTagStructure = mode => {
  30. tagStructure = (0, _getDefaultTagStructureForMode.default)(mode);
  31. };
  32. /**
  33. * @typedef {undefined|string|{
  34. * name: Integer,
  35. * restElement: boolean
  36. * }|{
  37. * isRestProperty: boolean|undefined,
  38. * name: string,
  39. * restElement: boolean
  40. * }|{
  41. * name: string,
  42. * restElement: boolean
  43. * }} ParamCommon
  44. */
  45. /**
  46. * @typedef {ParamCommon|[string|undefined, (FlattendRootInfo & {
  47. * annotationParamName?: string,
  48. * })]|NestedParamInfo} ParamNameInfo
  49. */
  50. /**
  51. * @typedef {{
  52. * hasPropertyRest: boolean,
  53. * hasRestElement: boolean,
  54. * names: string[],
  55. * rests: boolean[],
  56. * }} FlattendRootInfo
  57. */
  58. /**
  59. * @typedef {[string, (string[]|ParamInfo[])]} NestedParamInfo
  60. */
  61. /**
  62. * @typedef {ParamCommon|
  63. * [string|undefined, (FlattendRootInfo & {
  64. * annotationParamName?: string
  65. * })]|
  66. * NestedParamInfo} ParamInfo
  67. */
  68. /**
  69. * Given a nested array of property names, reduce them to a single array,
  70. * appending the name of the root element along the way if present.
  71. * @callback FlattenRoots
  72. * @param {ParamInfo[]} params
  73. * @param {string} [root]
  74. * @returns {FlattendRootInfo}
  75. */
  76. /** @type {FlattenRoots} */
  77. const flattenRoots = (params, root = '') => {
  78. let hasRestElement = false;
  79. let hasPropertyRest = false;
  80. /**
  81. * @type {boolean[]}
  82. */
  83. const rests = [];
  84. const names = params.reduce(
  85. /**
  86. * @param {string[]} acc
  87. * @param {ParamInfo} cur
  88. * @returns {string[]}
  89. */
  90. (acc, cur) => {
  91. if (Array.isArray(cur)) {
  92. let nms;
  93. if (Array.isArray(cur[1])) {
  94. nms = cur[1];
  95. } else {
  96. if (cur[1].hasRestElement) {
  97. hasRestElement = true;
  98. }
  99. if (cur[1].hasPropertyRest) {
  100. hasPropertyRest = true;
  101. }
  102. nms = cur[1].names;
  103. }
  104. const flattened = flattenRoots(nms, root ? `${root}.${cur[0]}` : cur[0]);
  105. if (flattened.hasRestElement) {
  106. hasRestElement = true;
  107. }
  108. if (flattened.hasPropertyRest) {
  109. hasPropertyRest = true;
  110. }
  111. const inner = /** @type {string[]} */[root ? `${root}.${cur[0]}` : cur[0], ...flattened.names].filter(Boolean);
  112. rests.push(false, ...flattened.rests);
  113. return acc.concat(inner);
  114. }
  115. if (typeof cur === 'object') {
  116. if ('isRestProperty' in cur && cur.isRestProperty) {
  117. hasPropertyRest = true;
  118. rests.push(true);
  119. } else {
  120. rests.push(false);
  121. }
  122. if ('restElement' in cur && cur.restElement) {
  123. hasRestElement = true;
  124. }
  125. acc.push(root ? `${root}.${String(cur.name)}` : String(cur.name));
  126. } else if (typeof cur !== 'undefined') {
  127. rests.push(false);
  128. acc.push(root ? `${root}.${cur}` : cur);
  129. }
  130. return acc;
  131. }, []);
  132. return {
  133. hasPropertyRest,
  134. hasRestElement,
  135. names,
  136. rests
  137. };
  138. };
  139. /**
  140. * @param {import('@typescript-eslint/types').TSESTree.TSIndexSignature|
  141. * import('@typescript-eslint/types').TSESTree.TSConstructSignatureDeclaration|
  142. * import('@typescript-eslint/types').TSESTree.TSCallSignatureDeclaration|
  143. * import('@typescript-eslint/types').TSESTree.TSPropertySignature} propSignature
  144. * @returns {undefined|string|[string, string[]]}
  145. */
  146. const getPropertiesFromPropertySignature = propSignature => {
  147. if (propSignature.type === 'TSIndexSignature' || propSignature.type === 'TSConstructSignatureDeclaration' || propSignature.type === 'TSCallSignatureDeclaration') {
  148. return undefined;
  149. }
  150. if (propSignature.typeAnnotation && propSignature.typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') {
  151. return [/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */propSignature.key.name, propSignature.typeAnnotation.typeAnnotation.members.map(member => {
  152. return /** @type {string} */(
  153. getPropertiesFromPropertySignature( /** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */
  154. member)
  155. );
  156. })];
  157. }
  158. return /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */propSignature.key.name;
  159. };
  160. /**
  161. * @param {ESTreeOrTypeScriptNode|null} functionNode
  162. * @param {boolean} [checkDefaultObjects]
  163. * @throws {Error}
  164. * @returns {ParamNameInfo[]}
  165. */
  166. const getFunctionParameterNames = (functionNode, checkDefaultObjects) => {
  167. var _functionNode$value;
  168. /* eslint-disable complexity -- Temporary */
  169. /**
  170. * @param {import('estree').Identifier|import('estree').AssignmentPattern|
  171. * import('estree').ObjectPattern|import('estree').Property|
  172. * import('estree').RestElement|import('estree').ArrayPattern|
  173. * import('@typescript-eslint/types').TSESTree.TSParameterProperty|
  174. * import('@typescript-eslint/types').TSESTree.Property|
  175. * import('@typescript-eslint/types').TSESTree.RestElement|
  176. * import('@typescript-eslint/types').TSESTree.Identifier|
  177. * import('@typescript-eslint/types').TSESTree.ObjectPattern|
  178. * import('@typescript-eslint/types').TSESTree.BindingName|
  179. * import('@typescript-eslint/types').TSESTree.Parameter
  180. * } param
  181. * @param {boolean} [isProperty]
  182. * @returns {ParamNameInfo|[string, ParamNameInfo[]]}
  183. */
  184. const getParamName = (param, isProperty) => {
  185. var _param$left2;
  186. /* eslint-enable complexity -- Temporary */
  187. const hasLeftTypeAnnotation = 'left' in param && 'typeAnnotation' in param.left;
  188. if ('typeAnnotation' in param || hasLeftTypeAnnotation) {
  189. var _typeAnnotation$typeA;
  190. const typeAnnotation = hasLeftTypeAnnotation ? /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */param.left.typeAnnotation : /** @type {import('@typescript-eslint/types').TSESTree.Identifier|import('@typescript-eslint/types').TSESTree.ObjectPattern} */
  191. param.typeAnnotation;
  192. if ((typeAnnotation === null || typeAnnotation === void 0 || (_typeAnnotation$typeA = typeAnnotation.typeAnnotation) === null || _typeAnnotation$typeA === void 0 ? void 0 : _typeAnnotation$typeA.type) === 'TSTypeLiteral') {
  193. const propertyNames = typeAnnotation.typeAnnotation.members.map(member => {
  194. return getPropertiesFromPropertySignature( /** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */
  195. member);
  196. });
  197. const flattened = {
  198. ...flattenRoots(propertyNames),
  199. annotationParamName: 'name' in param ? param.name : undefined
  200. };
  201. const hasLeftName = 'left' in param && 'name' in param.left;
  202. if ('name' in param || hasLeftName) {
  203. return [hasLeftName ? /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */param.left.name : /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */param.name, flattened];
  204. }
  205. return [undefined, flattened];
  206. }
  207. }
  208. if ('name' in param) {
  209. return param.name;
  210. }
  211. if ('left' in param && 'name' in param.left) {
  212. return param.left.name;
  213. }
  214. if (param.type === 'ObjectPattern' || 'left' in param && param.left.type === 'ObjectPattern') {
  215. var _param$left;
  216. const properties = /** @type {import('@typescript-eslint/types').TSESTree.ObjectPattern} */param.properties || ( /** @type {import('estree').ObjectPattern} */(_param$left = /** @type {import('@typescript-eslint/types').TSESTree.AssignmentPattern} */param.left) === null || _param$left === void 0 ? void 0 : _param$left.properties);
  217. const roots = properties.map(prop => {
  218. return getParamName(prop, true);
  219. });
  220. return [undefined, flattenRoots(roots)];
  221. }
  222. if (param.type === 'Property') {
  223. switch (param.value.type) {
  224. case 'ArrayPattern':
  225. {
  226. return [/** @type {import('estree').Identifier} */
  227. param.key.name, /** @type {import('estree').ArrayPattern} */param.value.elements.map((prop, idx) => {
  228. return {
  229. name: idx,
  230. restElement: (prop === null || prop === void 0 ? void 0 : prop.type) === 'RestElement'
  231. };
  232. })];
  233. }
  234. case 'ObjectPattern':
  235. {
  236. return [/** @type {import('estree').Identifier} */param.key.name, /** @type {import('estree').ObjectPattern} */param.value.properties.map(prop => {
  237. return /** @type {string|[string, string[]]} */getParamName(prop, isProperty);
  238. })];
  239. }
  240. case 'AssignmentPattern':
  241. {
  242. switch (param.value.left.type) {
  243. case 'Identifier':
  244. // Default parameter
  245. if (checkDefaultObjects && param.value.right.type === 'ObjectExpression') {
  246. return [/** @type {import('estree').Identifier} */param.key.name, /** @type {import('estree').AssignmentPattern} */param.value.right.properties.map(prop => {
  247. return /** @type {string} */getParamName( /** @type {import('estree').Property} */
  248. prop, isProperty);
  249. })];
  250. }
  251. break;
  252. case 'ObjectPattern':
  253. return [/** @type {import('estree').Identifier} */
  254. param.key.name, /** @type {import('estree').ObjectPattern} */param.value.left.properties.map(prop => {
  255. return getParamName(prop, isProperty);
  256. })];
  257. case 'ArrayPattern':
  258. return [/** @type {import('estree').Identifier} */
  259. param.key.name, /** @type {import('estree').ArrayPattern} */param.value.left.elements.map((prop, idx) => {
  260. return {
  261. name: idx,
  262. restElement: (prop === null || prop === void 0 ? void 0 : prop.type) === 'RestElement'
  263. };
  264. })];
  265. }
  266. }
  267. }
  268. switch (param.key.type) {
  269. case 'Identifier':
  270. return param.key.name;
  271. // The key of an object could also be a string or number
  272. case 'Literal':
  273. return /** @type {string} */param.key.raw ||
  274. // istanbul ignore next -- `raw` may not be present in all parsers
  275. param.key.value;
  276. // case 'MemberExpression':
  277. default:
  278. // Todo: We should really create a structure (and a corresponding
  279. // option analogous to `checkRestProperty`) which allows for
  280. // (and optionally requires) dynamic properties to have a single
  281. // line of documentation
  282. return undefined;
  283. }
  284. }
  285. if (param.type === 'ArrayPattern' || /** @type {import('estree').AssignmentPattern} */((_param$left2 = param.left) === null || _param$left2 === void 0 ? void 0 : _param$left2.type) === 'ArrayPattern') {
  286. var _param$left3;
  287. const elements = /** @type {import('estree').ArrayPattern} */param.elements || ( /** @type {import('estree').ArrayPattern} */(_param$left3 = /** @type {import('estree').AssignmentPattern} */param.left) === null || _param$left3 === void 0 ? void 0 : _param$left3.elements);
  288. const roots = elements.map((prop, idx) => {
  289. return {
  290. name: `"${idx}"`,
  291. restElement: (prop === null || prop === void 0 ? void 0 : prop.type) === 'RestElement'
  292. };
  293. });
  294. return [undefined, flattenRoots(roots)];
  295. }
  296. if (['RestElement', 'ExperimentalRestProperty'].includes(param.type)) {
  297. return {
  298. isRestProperty: isProperty,
  299. name: /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */( /** @type {import('@typescript-eslint/types').TSESTree.RestElement} */param.argument).name,
  300. restElement: true
  301. };
  302. }
  303. if (param.type === 'TSParameterProperty') {
  304. return getParamName( /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */
  305. /** @type {import('@typescript-eslint/types').TSESTree.TSParameterProperty} */param.parameter, true);
  306. }
  307. throw new Error(`Unsupported function signature format: \`${param.type}\`.`);
  308. };
  309. if (!functionNode) {
  310. return [];
  311. }
  312. return ( /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */functionNode.params || ( /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */(_functionNode$value = functionNode.value) === null || _functionNode$value === void 0 ? void 0 : _functionNode$value.params) || []).map(param => {
  313. return getParamName(param);
  314. });
  315. };
  316. /**
  317. * @param {ESTreeOrTypeScriptNode} functionNode
  318. * @returns {Integer}
  319. */
  320. const hasParams = functionNode => {
  321. // Should also check `functionNode.value.params` if supporting `MethodDefinition`
  322. return /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */functionNode.params.length;
  323. };
  324. /**
  325. * Gets all names of the target type, including those that refer to a path, e.g.
  326. * `foo` or `foo.bar`.
  327. * @param {import('comment-parser').Block} jsdoc
  328. * @param {string} targetTagName
  329. * @returns {{
  330. * idx: Integer,
  331. * name: string,
  332. * type: string
  333. * }[]}
  334. */
  335. const getJsdocTagsDeep = (jsdoc, targetTagName) => {
  336. const ret = [];
  337. for (const [idx, {
  338. name,
  339. tag,
  340. type
  341. }] of jsdoc.tags.entries()) {
  342. if (tag !== targetTagName) {
  343. continue;
  344. }
  345. ret.push({
  346. idx,
  347. name,
  348. type
  349. });
  350. }
  351. return ret;
  352. };
  353. const modeWarnSettings = (0, _WarnSettings.default)();
  354. /**
  355. * @param {ParserMode|undefined} mode
  356. * @param {import('eslint').Rule.RuleContext} context
  357. * @returns {import('./tagNames.js').AliasedTags}
  358. */
  359. const getTagNamesForMode = (mode, context) => {
  360. switch (mode) {
  361. case 'jsdoc':
  362. return _tagNames.jsdocTags;
  363. case 'typescript':
  364. return _tagNames.typeScriptTags;
  365. case 'closure':
  366. case 'permissive':
  367. return _tagNames.closureTags;
  368. default:
  369. if (!modeWarnSettings.hasBeenWarned(context, 'mode')) {
  370. context.report({
  371. loc: {
  372. end: {
  373. column: 1,
  374. line: 1
  375. },
  376. start: {
  377. column: 1,
  378. line: 1
  379. }
  380. },
  381. message: `Unrecognized value \`${mode}\` for \`settings.jsdoc.mode\`.`
  382. });
  383. modeWarnSettings.markSettingAsWarned(context, 'mode');
  384. }
  385. // We'll avoid breaking too many other rules
  386. return _tagNames.jsdocTags;
  387. }
  388. };
  389. /**
  390. * @param {import('eslint').Rule.RuleContext} context
  391. * @param {ParserMode|undefined} mode
  392. * @param {string} name
  393. * @param {TagNamePreference} tagPreference
  394. * @returns {string|false|{
  395. * message: string;
  396. * replacement?: string|undefined;
  397. * }}
  398. */
  399. const getPreferredTagName = (context, mode, name, tagPreference = {}) => {
  400. var _Object$entries$find;
  401. const prefValues = Object.values(tagPreference);
  402. if (prefValues.includes(name) || prefValues.some(prefVal => {
  403. return prefVal && typeof prefVal === 'object' && prefVal.replacement === name;
  404. })) {
  405. return name;
  406. }
  407. // Allow keys to have a 'tag ' prefix to avoid upstream bug in ESLint
  408. // that disallows keys that conflict with Object.prototype,
  409. // e.g. 'tag constructor' for 'constructor':
  410. // https://github.com/eslint/eslint/issues/13289
  411. // https://github.com/gajus/eslint-plugin-jsdoc/issues/537
  412. const tagPreferenceFixed = Object.fromEntries(Object.entries(tagPreference).map(([key, value]) => {
  413. return [key.replace(/^tag /u, ''), value];
  414. }));
  415. if (Object.prototype.hasOwnProperty.call(tagPreferenceFixed, name)) {
  416. return tagPreferenceFixed[name];
  417. }
  418. const tagNames = getTagNamesForMode(mode, context);
  419. const preferredTagName = (_Object$entries$find = Object.entries(tagNames).find(([, aliases]) => {
  420. return aliases.includes(name);
  421. })) === null || _Object$entries$find === void 0 ? void 0 : _Object$entries$find[0];
  422. if (preferredTagName) {
  423. return preferredTagName;
  424. }
  425. return name;
  426. };
  427. /**
  428. * @param {import('eslint').Rule.RuleContext} context
  429. * @param {ParserMode|undefined} mode
  430. * @param {string} name
  431. * @param {string[]} definedTags
  432. * @returns {boolean}
  433. */
  434. const isValidTag = (context, mode, name, definedTags) => {
  435. const tagNames = getTagNamesForMode(mode, context);
  436. const validTagNames = Object.keys(tagNames).concat(Object.values(tagNames).flat());
  437. const additionalTags = definedTags;
  438. const allTags = validTagNames.concat(additionalTags);
  439. return allTags.includes(name);
  440. };
  441. /**
  442. * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
  443. * @param {string} targetTagName
  444. * @returns {boolean}
  445. */
  446. const hasTag = (jsdoc, targetTagName) => {
  447. const targetTagLower = targetTagName.toLowerCase();
  448. return jsdoc.tags.some(doc => {
  449. return doc.tag.toLowerCase() === targetTagLower;
  450. });
  451. };
  452. /**
  453. * Get all tags, inline tags and inline tags in tags
  454. * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
  455. * @returns {(import('comment-parser').Spec|
  456. * import('@es-joy/jsdoccomment').JsdocInlineTagNoType)[]}
  457. */
  458. const getAllTags = jsdoc => {
  459. return [...jsdoc.tags, ...jsdoc.inlineTags.map(inlineTag => {
  460. // Tags don't have source or line numbers, so add before returning
  461. let line = -1;
  462. for (const {
  463. tokens: {
  464. description
  465. }
  466. } of jsdoc.source) {
  467. line++;
  468. if (description && description.includes(`{@${inlineTag.tag}`)) {
  469. break;
  470. }
  471. }
  472. inlineTag.line = line;
  473. return inlineTag;
  474. }), ...jsdoc.tags.flatMap(tag => {
  475. let tagBegins = -1;
  476. for (const {
  477. tokens: {
  478. tag: tg
  479. }
  480. } of jsdoc.source) {
  481. tagBegins++;
  482. if (tg) {
  483. break;
  484. }
  485. }
  486. for (const inlineTag of tag.inlineTags) {
  487. /** @type {import('./iterateJsdoc.js').Integer} */
  488. let line = 0;
  489. for (const {
  490. number,
  491. tokens: {
  492. description
  493. }
  494. } of tag.source) {
  495. if (description && description.includes(`{@${inlineTag.tag}`)) {
  496. line = number;
  497. break;
  498. }
  499. }
  500. inlineTag.line = tagBegins + line - 1;
  501. }
  502. return (
  503. /**
  504. * @type {import('comment-parser').Spec & {
  505. * inlineTags: import('@es-joy/jsdoccomment').JsdocInlineTagNoType[]
  506. * }}
  507. */
  508. tag.inlineTags
  509. );
  510. })];
  511. };
  512. /**
  513. * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
  514. * @param {string[]} targetTagNames
  515. * @returns {boolean}
  516. */
  517. const hasATag = (jsdoc, targetTagNames) => {
  518. return targetTagNames.some(targetTagName => {
  519. return hasTag(jsdoc, targetTagName);
  520. });
  521. };
  522. /**
  523. * Checks if the JSDoc comment has an undefined type.
  524. * @param {import('comment-parser').Spec|null|undefined} tag
  525. * the tag which should be checked.
  526. * @param {ParserMode} mode
  527. * @returns {boolean}
  528. * true in case a defined type is undeclared; otherwise false.
  529. */
  530. const mayBeUndefinedTypeTag = (tag, mode) => {
  531. // The function should not continue in the event the type is not defined...
  532. if (typeof tag === 'undefined' || tag === null) {
  533. return true;
  534. }
  535. // .. same applies if it declares an `{undefined}` or `{void}` type
  536. const tagType = tag.type.trim();
  537. // Exit early if matching
  538. if (tagType === 'undefined' || tagType === 'void' || tagType === '*' || tagType === 'any') {
  539. return true;
  540. }
  541. let parsedTypes;
  542. try {
  543. parsedTypes = (0, _jsdoccomment.tryParse)(tagType, mode === 'permissive' ? undefined : [mode]);
  544. } catch {
  545. // Ignore
  546. }
  547. if (
  548. // We do not traverse deeply as it could be, e.g., `Promise<void>`
  549. parsedTypes && parsedTypes.type === 'JsdocTypeUnion' && parsedTypes.elements.find(elem => {
  550. return elem.type === 'JsdocTypeUndefined' || elem.type === 'JsdocTypeName' && elem.value === 'void';
  551. })) {
  552. return true;
  553. }
  554. // In any other case, a type is present
  555. return false;
  556. };
  557. /**
  558. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} map
  559. * @param {string} tag
  560. * @returns {Map<string, string|string[]|boolean|undefined>}
  561. */
  562. const ensureMap = (map, tag) => {
  563. if (!map.has(tag)) {
  564. map.set(tag, new Map());
  565. }
  566. return /** @type {Map<string, string | boolean>} */map.get(tag);
  567. };
  568. /**
  569. * @param {import('./iterateJsdoc.js').StructuredTags} structuredTags
  570. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  571. * @returns {void}
  572. */
  573. const overrideTagStructure = (structuredTags, tagMap = tagStructure) => {
  574. for (const [tag, {
  575. name,
  576. type,
  577. required = []
  578. }] of Object.entries(structuredTags)) {
  579. const tagStruct = ensureMap(tagMap, tag);
  580. tagStruct.set('namepathRole', name);
  581. tagStruct.set('typeAllowed', type);
  582. const requiredName = required.includes('name');
  583. if (requiredName && name === false) {
  584. throw new Error('Cannot add "name" to `require` with the tag\'s `name` set to `false`');
  585. }
  586. tagStruct.set('nameRequired', requiredName);
  587. const requiredType = required.includes('type');
  588. if (requiredType && type === false) {
  589. throw new Error('Cannot add "type" to `require` with the tag\'s `type` set to `false`');
  590. }
  591. tagStruct.set('typeRequired', requiredType);
  592. const typeOrNameRequired = required.includes('typeOrNameRequired');
  593. if (typeOrNameRequired && name === false) {
  594. throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `name` set to `false`');
  595. }
  596. if (typeOrNameRequired && type === false) {
  597. throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `type` set to `false`');
  598. }
  599. tagStruct.set('typeOrNameRequired', typeOrNameRequired);
  600. }
  601. };
  602. /**
  603. * @param {ParserMode} mode
  604. * @param {import('./iterateJsdoc.js').StructuredTags} structuredTags
  605. * @returns {import('./getDefaultTagStructureForMode.js').TagStructure}
  606. */
  607. const getTagStructureForMode = (mode, structuredTags) => {
  608. const tagStruct = (0, _getDefaultTagStructureForMode.default)(mode);
  609. try {
  610. overrideTagStructure(structuredTags, tagStruct);
  611. } catch {
  612. //
  613. }
  614. return tagStruct;
  615. };
  616. /**
  617. * @param {string} tag
  618. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  619. * @returns {boolean}
  620. */
  621. const isNamepathDefiningTag = (tag, tagMap = tagStructure) => {
  622. const tagStruct = ensureMap(tagMap, tag);
  623. return tagStruct.get('namepathRole') === 'namepath-defining';
  624. };
  625. /**
  626. * @param {string} tag
  627. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  628. * @returns {boolean}
  629. */
  630. const isNamepathReferencingTag = (tag, tagMap = tagStructure) => {
  631. const tagStruct = ensureMap(tagMap, tag);
  632. return tagStruct.get('namepathRole') === 'namepath-referencing';
  633. };
  634. /**
  635. * @param {string} tag
  636. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  637. * @returns {boolean}
  638. */
  639. const isNamepathOrUrlReferencingTag = (tag, tagMap = tagStructure) => {
  640. const tagStruct = ensureMap(tagMap, tag);
  641. return tagStruct.get('namepathRole') === 'namepath-or-url-referencing';
  642. };
  643. /**
  644. * @param {string} tag
  645. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  646. * @returns {boolean|undefined}
  647. */
  648. const tagMustHaveTypePosition = (tag, tagMap = tagStructure) => {
  649. const tagStruct = ensureMap(tagMap, tag);
  650. return /** @type {boolean|undefined} */tagStruct.get('typeRequired');
  651. };
  652. /**
  653. * @param {string} tag
  654. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  655. * @returns {boolean|string}
  656. */
  657. const tagMightHaveTypePosition = (tag, tagMap = tagStructure) => {
  658. if (tagMustHaveTypePosition(tag, tagMap)) {
  659. return true;
  660. }
  661. const tagStruct = ensureMap(tagMap, tag);
  662. const ret = /** @type {boolean|undefined} */tagStruct.get('typeAllowed');
  663. return ret === undefined ? true : ret;
  664. };
  665. const namepathTypes = new Set(['namepath-defining', 'namepath-referencing']);
  666. /**
  667. * @param {string} tag
  668. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  669. * @returns {boolean}
  670. */
  671. const tagMightHaveNamePosition = (tag, tagMap = tagStructure) => {
  672. const tagStruct = ensureMap(tagMap, tag);
  673. const ret = tagStruct.get('namepathRole');
  674. return ret === undefined ? true : Boolean(ret);
  675. };
  676. /**
  677. * @param {string} tag
  678. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  679. * @returns {boolean}
  680. */
  681. const tagMightHaveNamepath = (tag, tagMap = tagStructure) => {
  682. const tagStruct = ensureMap(tagMap, tag);
  683. const nampathRole = tagStruct.get('namepathRole');
  684. return nampathRole !== false && namepathTypes.has( /** @type {string} */nampathRole);
  685. };
  686. /**
  687. * @param {string} tag
  688. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  689. * @returns {boolean|undefined}
  690. */
  691. const tagMustHaveNamePosition = (tag, tagMap = tagStructure) => {
  692. const tagStruct = ensureMap(tagMap, tag);
  693. return /** @type {boolean|undefined} */tagStruct.get('nameRequired');
  694. };
  695. /**
  696. * @param {string} tag
  697. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  698. * @returns {boolean}
  699. */
  700. const tagMightHaveEitherTypeOrNamePosition = (tag, tagMap) => {
  701. return Boolean(tagMightHaveTypePosition(tag, tagMap)) || tagMightHaveNamepath(tag, tagMap);
  702. };
  703. /**
  704. * @param {string} tag
  705. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  706. * @returns {boolean|undefined}
  707. */
  708. const tagMustHaveEitherTypeOrNamePosition = (tag, tagMap) => {
  709. const tagStruct = ensureMap(tagMap, tag);
  710. return /** @type {boolean} */tagStruct.get('typeOrNameRequired');
  711. };
  712. /**
  713. * @param {import('comment-parser').Spec} tag
  714. * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap
  715. * @returns {boolean|undefined}
  716. */
  717. const tagMissingRequiredTypeOrNamepath = (tag, tagMap = tagStructure) => {
  718. const mustHaveTypePosition = tagMustHaveTypePosition(tag.tag, tagMap);
  719. const mightHaveTypePosition = tagMightHaveTypePosition(tag.tag, tagMap);
  720. const hasTypePosition = mightHaveTypePosition && Boolean(tag.type);
  721. const hasNameOrNamepathPosition = (tagMustHaveNamePosition(tag.tag, tagMap) || tagMightHaveNamepath(tag.tag, tagMap)) && Boolean(tag.name);
  722. const mustHaveEither = tagMustHaveEitherTypeOrNamePosition(tag.tag, tagMap);
  723. const hasEither = tagMightHaveEitherTypeOrNamePosition(tag.tag, tagMap) && (hasTypePosition || hasNameOrNamepathPosition);
  724. return mustHaveEither && !hasEither && !mustHaveTypePosition;
  725. };
  726. /* eslint-disable complexity -- Temporary */
  727. /**
  728. * @param {ESTreeOrTypeScriptNode|null|undefined} node
  729. * @param {boolean} [checkYieldReturnValue]
  730. * @returns {boolean}
  731. */
  732. const hasNonFunctionYield = (node, checkYieldReturnValue) => {
  733. /* eslint-enable complexity -- Temporary */
  734. if (!node) {
  735. return false;
  736. }
  737. switch (node.type) {
  738. case 'BlockStatement':
  739. {
  740. return node.body.some(bodyNode => {
  741. return !['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(bodyNode.type) && hasNonFunctionYield(bodyNode, checkYieldReturnValue);
  742. });
  743. }
  744. // @ts-expect-error In Babel?
  745. // istanbul ignore next -- In Babel?
  746. case 'OptionalCallExpression':
  747. case 'CallExpression':
  748. return node.arguments.some(element => {
  749. return hasNonFunctionYield(element, checkYieldReturnValue);
  750. });
  751. case 'ChainExpression':
  752. case 'ExpressionStatement':
  753. {
  754. return hasNonFunctionYield(node.expression, checkYieldReturnValue);
  755. }
  756. case 'LabeledStatement':
  757. case 'WhileStatement':
  758. case 'DoWhileStatement':
  759. case 'ForStatement':
  760. case 'ForInStatement':
  761. case 'ForOfStatement':
  762. case 'WithStatement':
  763. {
  764. return hasNonFunctionYield(node.body, checkYieldReturnValue);
  765. }
  766. case 'ConditionalExpression':
  767. case 'IfStatement':
  768. {
  769. return hasNonFunctionYield(node.test, checkYieldReturnValue) || hasNonFunctionYield(node.consequent, checkYieldReturnValue) || hasNonFunctionYield(node.alternate, checkYieldReturnValue);
  770. }
  771. case 'TryStatement':
  772. {
  773. return hasNonFunctionYield(node.block, checkYieldReturnValue) || hasNonFunctionYield(node.handler && node.handler.body, checkYieldReturnValue) || hasNonFunctionYield( /** @type {import('@typescript-eslint/types').TSESTree.BlockStatement} */
  774. node.finalizer, checkYieldReturnValue);
  775. }
  776. case 'SwitchStatement':
  777. {
  778. return node.cases.some(someCase => {
  779. return someCase.consequent.some(nde => {
  780. return hasNonFunctionYield(nde, checkYieldReturnValue);
  781. });
  782. });
  783. }
  784. case 'ArrayPattern':
  785. case 'ArrayExpression':
  786. return node.elements.some(element => {
  787. return hasNonFunctionYield(element, checkYieldReturnValue);
  788. });
  789. case 'AssignmentPattern':
  790. return hasNonFunctionYield(node.right, checkYieldReturnValue);
  791. case 'VariableDeclaration':
  792. {
  793. return node.declarations.some(nde => {
  794. return hasNonFunctionYield(nde, checkYieldReturnValue);
  795. });
  796. }
  797. case 'VariableDeclarator':
  798. {
  799. return hasNonFunctionYield(node.id, checkYieldReturnValue) || hasNonFunctionYield(node.init, checkYieldReturnValue);
  800. }
  801. case 'AssignmentExpression':
  802. case 'BinaryExpression':
  803. case 'LogicalExpression':
  804. {
  805. return hasNonFunctionYield(node.left, checkYieldReturnValue) || hasNonFunctionYield(node.right, checkYieldReturnValue);
  806. }
  807. // Comma
  808. case 'SequenceExpression':
  809. case 'TemplateLiteral':
  810. return node.expressions.some(subExpression => {
  811. return hasNonFunctionYield(subExpression, checkYieldReturnValue);
  812. });
  813. case 'ObjectPattern':
  814. case 'ObjectExpression':
  815. return node.properties.some(property => {
  816. return hasNonFunctionYield(property, checkYieldReturnValue);
  817. });
  818. // istanbul ignore next -- In Babel?
  819. case 'PropertyDefinition':
  820. /* eslint-disable no-fallthrough */
  821. // @ts-expect-error In Babel?
  822. // istanbul ignore next -- In Babel?
  823. case 'ObjectProperty':
  824. // @ts-expect-error In Babel?
  825. // istanbul ignore next -- In Babel?
  826. case 'ClassProperty':
  827. case 'Property':
  828. /* eslint-enable no-fallthrough */
  829. return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || hasNonFunctionYield(node.value, checkYieldReturnValue);
  830. // @ts-expect-error In Babel?
  831. // istanbul ignore next -- In Babel?
  832. case 'ObjectMethod':
  833. // @ts-expect-error In Babel?
  834. // istanbul ignore next -- In Babel?
  835. return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) ||
  836. // @ts-expect-error In Babel?
  837. node.arguments.some(nde => {
  838. return hasNonFunctionYield(nde, checkYieldReturnValue);
  839. });
  840. case 'SpreadElement':
  841. case 'UnaryExpression':
  842. return hasNonFunctionYield(node.argument, checkYieldReturnValue);
  843. case 'TaggedTemplateExpression':
  844. return hasNonFunctionYield(node.quasi, checkYieldReturnValue);
  845. // @ts-expect-error In Babel?
  846. // ?.
  847. // istanbul ignore next -- In Babel?
  848. case 'OptionalMemberExpression':
  849. case 'MemberExpression':
  850. return hasNonFunctionYield(node.object, checkYieldReturnValue) || hasNonFunctionYield(node.property, checkYieldReturnValue);
  851. // @ts-expect-error In Babel?
  852. // istanbul ignore next -- In Babel?
  853. case 'Import':
  854. case 'ImportExpression':
  855. return hasNonFunctionYield(node.source, checkYieldReturnValue);
  856. case 'ReturnStatement':
  857. {
  858. if (node.argument === null) {
  859. return false;
  860. }
  861. return hasNonFunctionYield(node.argument, checkYieldReturnValue);
  862. }
  863. case 'YieldExpression':
  864. {
  865. if (checkYieldReturnValue) {
  866. if ( /** @type {import('eslint').Rule.Node} */node.parent.type === 'VariableDeclarator') {
  867. return true;
  868. }
  869. return false;
  870. }
  871. // void return does not count.
  872. if (node.argument === null) {
  873. return false;
  874. }
  875. return true;
  876. }
  877. default:
  878. {
  879. return false;
  880. }
  881. }
  882. };
  883. /**
  884. * Checks if a node has a return statement. Void return does not count.
  885. * @param {ESTreeOrTypeScriptNode} node
  886. * @param {boolean} [checkYieldReturnValue]
  887. * @returns {boolean}
  888. */
  889. const hasYieldValue = (node, checkYieldReturnValue) => {
  890. return /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */node.generator && ( /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */node.expression || hasNonFunctionYield( /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */
  891. node.body, checkYieldReturnValue));
  892. };
  893. /**
  894. * Checks if a node has a throws statement.
  895. * @param {ESTreeOrTypeScriptNode|null|undefined} node
  896. * @param {boolean} [innerFunction]
  897. * @returns {boolean}
  898. */
  899. // eslint-disable-next-line complexity
  900. const hasThrowValue = (node, innerFunction) => {
  901. if (!node) {
  902. return false;
  903. }
  904. // There are cases where a function may execute its inner function which
  905. // throws, but we're treating functions atomically rather than trying to
  906. // follow them
  907. switch (node.type) {
  908. case 'FunctionExpression':
  909. case 'FunctionDeclaration':
  910. case 'ArrowFunctionExpression':
  911. {
  912. return !innerFunction && !node.async && hasThrowValue(node.body, true);
  913. }
  914. case 'BlockStatement':
  915. {
  916. return node.body.some(bodyNode => {
  917. return bodyNode.type !== 'FunctionDeclaration' && hasThrowValue(bodyNode);
  918. });
  919. }
  920. case 'LabeledStatement':
  921. case 'WhileStatement':
  922. case 'DoWhileStatement':
  923. case 'ForStatement':
  924. case 'ForInStatement':
  925. case 'ForOfStatement':
  926. case 'WithStatement':
  927. {
  928. return hasThrowValue(node.body);
  929. }
  930. case 'IfStatement':
  931. {
  932. return hasThrowValue(node.consequent) || hasThrowValue(node.alternate);
  933. }
  934. // We only consider it to throw an error if the catch or finally blocks throw an error.
  935. case 'TryStatement':
  936. {
  937. return hasThrowValue(node.handler && node.handler.body) || hasThrowValue(node.finalizer);
  938. }
  939. case 'SwitchStatement':
  940. {
  941. return node.cases.some(someCase => {
  942. return someCase.consequent.some(nde => {
  943. return hasThrowValue(nde);
  944. });
  945. });
  946. }
  947. case 'ThrowStatement':
  948. {
  949. return true;
  950. }
  951. default:
  952. {
  953. return false;
  954. }
  955. }
  956. };
  957. /**
  958. * @param {string} tag
  959. */
  960. /*
  961. const isInlineTag = (tag) => {
  962. return /^(@link|@linkcode|@linkplain|@tutorial) /u.test(tag);
  963. };
  964. */
  965. /**
  966. * Parses GCC Generic/Template types
  967. * @see {@link https://github.com/google/closure-compiler/wiki/Generic-Types}
  968. * @see {@link https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template}
  969. * @param {import('comment-parser').Spec} tag
  970. * @returns {string[]}
  971. */
  972. const parseClosureTemplateTag = tag => {
  973. return tag.name.split(',').map(type => {
  974. return type.trim().replace(/^\[(?<name>.*?)=.*\]$/u, '$<name>');
  975. });
  976. };
  977. /**
  978. * @typedef {true|string[]} DefaultContexts
  979. */
  980. /**
  981. * Checks user option for `contexts` array, defaulting to
  982. * contexts designated by the rule. Returns an array of
  983. * ESTree AST types, indicating allowable contexts.
  984. * @param {import('eslint').Rule.RuleContext} context
  985. * @param {DefaultContexts|undefined} defaultContexts
  986. * @param {{
  987. * contexts?: import('./iterateJsdoc.js').Context[]
  988. * }} settings
  989. * @returns {string[]}
  990. */
  991. const enforcedContexts = (context, defaultContexts, settings) => {
  992. var _context$options$;
  993. const contexts = ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.contexts) || settings.contexts || (defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression', 'TSDeclareFunction'] : defaultContexts);
  994. return contexts;
  995. };
  996. /**
  997. * @param {import('./iterateJsdoc.js').Context[]} contexts
  998. * @param {import('./iterateJsdoc.js').CheckJsdoc} checkJsdoc
  999. * @param {import('@es-joy/jsdoccomment').CommentHandler} [handler]
  1000. * @returns {import('eslint').Rule.RuleListener}
  1001. */
  1002. const getContextObject = (contexts, checkJsdoc, handler) => {
  1003. /** @type {import('eslint').Rule.RuleListener} */
  1004. const properties = {};
  1005. for (const [idx, prop] of contexts.entries()) {
  1006. /** @type {string} */
  1007. let property;
  1008. /** @type {(node: import('eslint').Rule.Node) => void} */
  1009. let value;
  1010. if (typeof prop === 'object') {
  1011. const selInfo = {
  1012. lastIndex: idx,
  1013. selector: prop.context
  1014. };
  1015. if (prop.comment) {
  1016. property = /** @type {string} */prop.context;
  1017. value = checkJsdoc.bind(null, {
  1018. ...selInfo,
  1019. comment: prop.comment
  1020. },
  1021. /**
  1022. * @type {(jsdoc: import('@es-joy/jsdoccomment').JsdocBlockWithInline) => boolean}
  1023. */
  1024. /** @type {import('@es-joy/jsdoccomment').CommentHandler} */
  1025. handler.bind(null, prop.comment));
  1026. } else {
  1027. property = /** @type {string} */prop.context;
  1028. value = checkJsdoc.bind(null, selInfo, null);
  1029. }
  1030. } else {
  1031. const selInfo = {
  1032. lastIndex: idx,
  1033. selector: prop
  1034. };
  1035. property = prop;
  1036. value = checkJsdoc.bind(null, selInfo, null);
  1037. }
  1038. const old =
  1039. /**
  1040. * @type {((node: import('eslint').Rule.Node) => void)}
  1041. */
  1042. properties[property];
  1043. properties[property] = old ?
  1044. /**
  1045. * @type {((node: import('eslint').Rule.Node) => void)}
  1046. */
  1047. function (node) {
  1048. old(node);
  1049. value(node);
  1050. } : value;
  1051. }
  1052. return properties;
  1053. };
  1054. const tagsWithNamesAndDescriptions = new Set(['param', 'arg', 'argument', 'property', 'prop', 'template',
  1055. // These two are parsed by our custom parser as though having a `name`
  1056. 'returns', 'return']);
  1057. /**
  1058. * @typedef {{
  1059. * [key: string]: false|string|
  1060. * {message: string, replacement?: string}
  1061. * }} TagNamePreference
  1062. */
  1063. /**
  1064. * @param {import('eslint').Rule.RuleContext} context
  1065. * @param {ParserMode|undefined} mode
  1066. * @param {import('comment-parser').Spec[]} tags
  1067. * @returns {{
  1068. * tagsWithNames: import('comment-parser').Spec[],
  1069. * tagsWithoutNames: import('comment-parser').Spec[]
  1070. * }}
  1071. */
  1072. const getTagsByType = (context, mode, tags) => {
  1073. /**
  1074. * @type {import('comment-parser').Spec[]}
  1075. */
  1076. const tagsWithoutNames = [];
  1077. const tagsWithNames = tags.filter(tag => {
  1078. const {
  1079. tag: tagName
  1080. } = tag;
  1081. const tagWithName = tagsWithNamesAndDescriptions.has(tagName);
  1082. if (!tagWithName) {
  1083. tagsWithoutNames.push(tag);
  1084. }
  1085. return tagWithName;
  1086. });
  1087. return {
  1088. tagsWithNames,
  1089. tagsWithoutNames
  1090. };
  1091. };
  1092. /**
  1093. * @param {import('eslint').SourceCode|{
  1094. * text: string
  1095. * }} sourceCode
  1096. * @returns {string}
  1097. */
  1098. const getIndent = sourceCode => {
  1099. var _sourceCode$text$matc;
  1100. return (((_sourceCode$text$matc = sourceCode.text.match(/^\n*([ \t]+)/u)) === null || _sourceCode$text$matc === void 0 ? void 0 : _sourceCode$text$matc[1]) ?? '') + ' ';
  1101. };
  1102. /**
  1103. * @param {import('eslint').Rule.Node|null} node
  1104. * @returns {boolean}
  1105. */
  1106. const isConstructor = node => {
  1107. var _node$parent;
  1108. return (node === null || node === void 0 ? void 0 : node.type) === 'MethodDefinition' && node.kind === 'constructor' || /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */(node === null || node === void 0 || (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.kind) === 'constructor';
  1109. };
  1110. /**
  1111. * @param {import('eslint').Rule.Node|null} node
  1112. * @returns {boolean}
  1113. */
  1114. const isGetter = node => {
  1115. var _node$parent2;
  1116. return node !== null &&
  1117. /**
  1118. * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
  1119. * import('@typescript-eslint/types').TSESTree.Property}
  1120. */
  1121. ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.kind) === 'get';
  1122. };
  1123. /**
  1124. * @param {import('eslint').Rule.Node|null} node
  1125. * @returns {boolean}
  1126. */
  1127. const isSetter = node => {
  1128. var _node$parent3;
  1129. return node !== null &&
  1130. /**
  1131. * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
  1132. * import('@typescript-eslint/types').TSESTree.Property}
  1133. */
  1134. ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.kind) === 'set';
  1135. };
  1136. /**
  1137. * @param {import('eslint').Rule.Node} node
  1138. * @returns {boolean}
  1139. */
  1140. const hasAccessorPair = node => {
  1141. const {
  1142. type,
  1143. kind: sourceKind,
  1144. key
  1145. } =
  1146. /**
  1147. * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
  1148. * import('@typescript-eslint/types').TSESTree.Property}
  1149. */
  1150. node;
  1151. const sourceName = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */key.name;
  1152. const oppositeKind = sourceKind === 'get' ? 'set' : 'get';
  1153. const sibling = type === 'MethodDefinition' ? /** @type {import('@typescript-eslint/types').TSESTree.ClassBody} */node.parent.body : /** @type {import('@typescript-eslint/types').TSESTree.ObjectExpression} */node.parent.properties;
  1154. return sibling.some(child => {
  1155. const {
  1156. kind,
  1157. key: ky
  1158. } =
  1159. /**
  1160. * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition|
  1161. * import('@typescript-eslint/types').TSESTree.Property}
  1162. */
  1163. child;
  1164. const name = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ky.name;
  1165. return kind === oppositeKind && name === sourceName;
  1166. });
  1167. };
  1168. /**
  1169. * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
  1170. * @param {import('eslint').Rule.Node|null} node
  1171. * @param {import('eslint').Rule.RuleContext} context
  1172. * @param {import('json-schema').JSONSchema4} schema
  1173. * @returns {boolean}
  1174. */
  1175. const exemptSpeciaMethods = (jsdoc, node, context, schema) => {
  1176. /**
  1177. * @param {"checkGetters"|"checkSetters"|"checkConstructors"} prop
  1178. * @returns {boolean|"no-setter"|"no-getter"}
  1179. */
  1180. const hasSchemaOption = prop => {
  1181. var _context$options$2;
  1182. const schemaProperties = schema[0].properties;
  1183. return ((_context$options$2 = context.options[0]) === null || _context$options$2 === void 0 ? void 0 : _context$options$2[prop]) ?? (schemaProperties[prop] && schemaProperties[prop].default);
  1184. };
  1185. const checkGetters = hasSchemaOption('checkGetters');
  1186. const checkSetters = hasSchemaOption('checkSetters');
  1187. return !hasSchemaOption('checkConstructors') && (isConstructor(node) || hasATag(jsdoc, ['class', 'constructor'])) || isGetter(node) && (!checkGetters || checkGetters === 'no-setter' && hasAccessorPair( /** @type {import('./iterateJsdoc.js').Node} */node.parent)) || isSetter(node) && (!checkSetters || checkSetters === 'no-getter' && hasAccessorPair( /** @type {import('./iterateJsdoc.js').Node} */node.parent));
  1188. };
  1189. /**
  1190. * Since path segments may be unquoted (if matching a reserved word,
  1191. * identifier or numeric literal) or single or double quoted, in either
  1192. * the `@param` or in source, we need to strip the quotes to give a fair
  1193. * comparison.
  1194. * @param {string} str
  1195. * @returns {string}
  1196. */
  1197. const dropPathSegmentQuotes = str => {
  1198. return str.replaceAll(/\.(['"])(.*)\1/gu, '.$2');
  1199. };
  1200. /**
  1201. * @param {string} name
  1202. * @returns {(otherPathName: string) => boolean}
  1203. */
  1204. const comparePaths = name => {
  1205. return otherPathName => {
  1206. return otherPathName === name || dropPathSegmentQuotes(otherPathName) === dropPathSegmentQuotes(name);
  1207. };
  1208. };
  1209. /**
  1210. * @callback PathDoesNotBeginWith
  1211. * @param {string} name
  1212. * @param {string} otherPathName
  1213. * @returns {boolean}
  1214. */
  1215. /** @type {PathDoesNotBeginWith} */
  1216. const pathDoesNotBeginWith = (name, otherPathName) => {
  1217. return !name.startsWith(otherPathName) && !dropPathSegmentQuotes(name).startsWith(dropPathSegmentQuotes(otherPathName));
  1218. };
  1219. /**
  1220. * @param {string} regexString
  1221. * @param {string} [requiredFlags]
  1222. * @returns {RegExp}
  1223. */
  1224. const getRegexFromString = (regexString, requiredFlags) => {
  1225. const match = regexString.match(/^\/(.*)\/([gimyus]*)$/us);
  1226. let flags = 'u';
  1227. let regex = regexString;
  1228. if (match) {
  1229. [, regex, flags] = match;
  1230. if (!flags) {
  1231. flags = 'u';
  1232. }
  1233. }
  1234. const uniqueFlags = [...new Set(flags + (requiredFlags || ''))];
  1235. flags = uniqueFlags.join('');
  1236. return new RegExp(regex, flags);
  1237. };
  1238. var _default = exports.default = {
  1239. comparePaths,
  1240. dropPathSegmentQuotes,
  1241. enforcedContexts,
  1242. exemptSpeciaMethods,
  1243. flattenRoots,
  1244. getAllTags,
  1245. getContextObject,
  1246. getFunctionParameterNames,
  1247. getIndent,
  1248. getJsdocTagsDeep,
  1249. getPreferredTagName,
  1250. getRegexFromString,
  1251. getTagsByType,
  1252. getTagStructureForMode,
  1253. hasATag,
  1254. hasParams,
  1255. hasReturnValue: _hasReturnValue.hasReturnValue,
  1256. hasTag,
  1257. hasThrowValue,
  1258. hasValueOrExecutorHasNonEmptyResolveValue: _hasReturnValue.hasValueOrExecutorHasNonEmptyResolveValue,
  1259. hasYieldValue,
  1260. isConstructor,
  1261. isGetter,
  1262. isNamepathDefiningTag,
  1263. isNamepathOrUrlReferencingTag,
  1264. isNamepathReferencingTag,
  1265. isSetter,
  1266. isValidTag,
  1267. mayBeUndefinedTypeTag,
  1268. overrideTagStructure,
  1269. parseClosureTemplateTag,
  1270. pathDoesNotBeginWith,
  1271. setTagStructure,
  1272. tagMightHaveEitherTypeOrNamePosition,
  1273. tagMightHaveNamepath,
  1274. tagMightHaveNamePosition,
  1275. tagMightHaveTypePosition,
  1276. tagMissingRequiredTypeOrNamepath,
  1277. tagMustHaveNamePosition,
  1278. tagMustHaveTypePosition
  1279. };
  1280. module.exports = exports.default;
  1281. //# sourceMappingURL=jsdocUtils.js.map