From 270fbdcb2410c5c59bd83f0c07d87e751dd0f181 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Thu, 25 Jul 2024 23:02:17 +0800 Subject: [PATCH] refactor: make exports named and move utils to own file for reuse --- src/iterateJsdoc.js | 114 +++---------------- src/jsdocUtils.js | 166 +++++++++++++++++++++++++++- src/rules/convertToJsdocComments.js | 16 ++- src/rules/requireJsdoc.js | 24 ++-- test/jsdocUtils.js | 14 +-- 5 files changed, 214 insertions(+), 120 deletions(-) diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index 9eeddf9b0..75df9b270 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -1,4 +1,4 @@ -import jsdocUtils from './jsdocUtils.js'; +import * as jsdocUtils from './jsdocUtils.js'; import { commentHandler, getJSDocComment, @@ -628,7 +628,7 @@ const getBasicUtils = (context, { utils.getPreferredTagNameObject = ({ tagName, }) => { - const ret = jsdocUtils.getPreferredTagName( + const ret = jsdocUtils.getPreferredTagNameSimple( context, /** @type {import('./jsdocUtils.js').ParserMode} */ (mode), tagName, @@ -783,44 +783,7 @@ const getUtils = ( /** @type {GetTagDescription} */ utils.getTagDescription = (tg, returnArray) => { - /** - * @type {string[]} - */ - const descriptions = []; - tg.source.some(({ - tokens: { - end, - lineEnd, - postDelimiter, - tag, - postTag, - name, - type, - description, - }, - }) => { - const desc = ( - tag && postTag || - !tag && !name && !type && postDelimiter || '' - - // Remove space - ).slice(1) + - (description || '') + (lineEnd || ''); - - if (end) { - if (desc) { - descriptions.push(desc); - } - - return true; - } - - descriptions.push(desc); - - return false; - }); - - return returnArray ? descriptions : descriptions.join('\n'); + return jsdocUtils.getTagDescription(tg, returnArray); }; /** @type {SetTagDescription} */ @@ -1375,29 +1338,11 @@ const getUtils = ( }; /** @type {GetPreferredTagName} */ - utils.getPreferredTagName = ({ - tagName, - skipReportingBlockedTag = false, - allowObjectReturn = false, - defaultMessage = `Unexpected tag \`@${tagName}\``, - }) => { - const ret = jsdocUtils.getPreferredTagName(context, mode, tagName, tagNamePreference); - const isObject = ret && typeof ret === 'object'; - if (utils.hasTag(tagName) && (ret === false || isObject && !ret.replacement)) { - if (skipReportingBlockedTag) { - return { - blocked: true, - tagName, - }; - } - - const message = isObject && ret.message || defaultMessage; - report(message, null, utils.getTags(tagName)[0]); - - return false; - } - - return isObject && !allowObjectReturn ? ret.replacement : ret; + utils.getPreferredTagName = (args) => { + return jsdocUtils.getPreferredTagName( + context, mode, report, tagNamePreference, + jsdoc, args + ); }; /** @type {IsValidTag} */ @@ -1619,21 +1564,19 @@ const getUtils = ( /** @type {GetTags} */ utils.getTags = (tagName) => { - return utils.filterTags((item) => { - return item.tag === tagName; - }); + return jsdocUtils.getTags(jsdoc, tagName); }; /** @type {GetPresentTags} */ utils.getPresentTags = (tagList) => { - return utils.filterTags((tag) => { + return jsdocUtils.filterTags(jsdoc, (tag) => { return tagList.includes(tag.tag); }); }; /** @type {FilterTags} */ utils.filterTags = (filter) => { - return jsdoc.tags.filter((tag) => { + return jsdocUtils.filterTags(jsdoc, (tag) => { return filter(tag); }); }; @@ -1699,34 +1642,11 @@ const getUtils = ( }; /** @type {ForEachPreferredTag} */ - utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag = false) => { - const targetTagName = /** @type {string|false} */ ( - utils.getPreferredTagName({ - skipReportingBlockedTag, - tagName, - }) + utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag) => { + return jsdocUtils.forEachPreferredTag( + context, mode, report, tagNamePreference, + jsdoc, tagName, arrayHandler, skipReportingBlockedTag ); - if (!targetTagName || - skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object' - ) { - return; - } - - const matchingJsdocTags = jsdoc.tags.filter(({ - tag, - }) => { - return tag === targetTagName; - }); - - for (const matchingJsdocTag of matchingJsdocTags) { - arrayHandler( - /** - * @type {import('@es-joy/jsdoccomment').JsdocTagWithInline} - */ ( - matchingJsdocTag - ), targetTagName, - ); - } }; /** @type {FindContext} */ @@ -2030,8 +1950,8 @@ const iterate = ( !ruleConfig.checkPrivate && settings.ignorePrivate && ( utils.hasTag('private') || - jsdoc.tags - .filter(({ + jsdocUtils + .filterTags(jsdoc, ({ tag, }) => { return tag === 'access'; diff --git a/src/jsdocUtils.js b/src/jsdocUtils.js index 96ef6ac92..59ebe6a9e 100644 --- a/src/jsdocUtils.js +++ b/src/jsdocUtils.js @@ -555,6 +555,52 @@ const getTagNamesForMode = (mode, context) => { } }; +/** + * @param {import('comment-parser').Spec} tg + * @param {boolean} [returnArray] + * @returns {string[]|string} + */ +const getTagDescription = (tg, returnArray) => { + /** + * @type {string[]} + */ + const descriptions = []; + tg.source.some(({ + tokens: { + end, + lineEnd, + postDelimiter, + tag, + postTag, + name, + type, + description, + }, + }) => { + const desc = ( + tag && postTag || + !tag && !name && !type && postDelimiter || '' + + // Remove space + ).slice(1) + + (description || '') + (lineEnd || ''); + + if (end) { + if (desc) { + descriptions.push(desc); + } + + return true; + } + + descriptions.push(desc); + + return false; + }); + + return returnArray ? descriptions : descriptions.join('\n'); +}; + /** * @param {import('eslint').Rule.RuleContext} context * @param {ParserMode|undefined} mode @@ -565,7 +611,7 @@ const getTagNamesForMode = (mode, context) => { * replacement?: string|undefined; * }} */ -const getPreferredTagName = ( +const getPreferredTagNameSimple = ( context, mode, name, @@ -649,6 +695,117 @@ const hasTag = (jsdoc, targetTagName) => { }); }; +/** + * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc + * @param {(tag: import('@es-joy/jsdoccomment').JsdocTagWithInline) => boolean} filter + * @returns {import('@es-joy/jsdoccomment').JsdocTagWithInline[]} + */ +const filterTags = (jsdoc, filter) => { + return jsdoc.tags.filter((tag) => { + return filter(tag); + }); +}; + +/** + * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc + * @param {string} tagName + * @returns {import('comment-parser').Spec[]} + */ +const getTags = (jsdoc, tagName) => { + return filterTags(jsdoc, (item) => { + return item.tag === tagName; + }); +}; + +/** + * @param {import('eslint').Rule.RuleContext} context + * @param {ParserMode} mode + * @param {import('./iterateJsdoc.js').Report} report + * @param {TagNamePreference} tagNamePreference + * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc + * @param {{ + * tagName: string, + * skipReportingBlockedTag?: boolean, + * allowObjectReturn?: boolean, + * defaultMessage?: string + * }} cfg + * @returns {string|undefined|false|{ + * message: string; + * replacement?: string|undefined; + * }|{ + * blocked: true, + * tagName: string + * }} + */ +const getPreferredTagName = (context, mode, report, tagNamePreference, jsdoc, { + tagName, + skipReportingBlockedTag = false, + allowObjectReturn = false, + defaultMessage = `Unexpected tag \`@${tagName}\``, +}) => { + const ret = getPreferredTagNameSimple(context, mode, tagName, tagNamePreference); + const isObject = ret && typeof ret === 'object'; + if (hasTag(jsdoc, tagName) && (ret === false || isObject && !ret.replacement)) { + if (skipReportingBlockedTag) { + return { + blocked: true, + tagName, + }; + } + + const message = isObject && ret.message || defaultMessage; + report(message, null, getTags(jsdoc, tagName)[0]); + + return false; + } + + return isObject && !allowObjectReturn ? ret.replacement : ret; +}; + +/** + * @param {import('eslint').Rule.RuleContext} context + * @param {ParserMode} mode + * @param {import('./iterateJsdoc.js').Report} report + * @param {TagNamePreference} tagNamePreference + * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc + * @param {string} tagName + * @param {( +* matchingJsdocTag: import('@es-joy/jsdoccomment').JsdocTagWithInline, +* targetTagName: string +* ) => void} arrayHandler +* @param {boolean} [skipReportingBlockedTag] +* @returns {void} +*/ +const forEachPreferredTag = (context, mode, report, tagNamePreference, jsdoc, tagName, arrayHandler, skipReportingBlockedTag = false) => { + const targetTagName = /** @type {string|false} */ ( + getPreferredTagName(context, mode, report, tagNamePreference, jsdoc, { + skipReportingBlockedTag, + tagName, + }) + ); + if (!targetTagName || + skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object' + ) { + return; + } + + const matchingJsdocTags = jsdoc.tags.filter(({ + tag, + }) => { + return tag === targetTagName; + }); + + for (const matchingJsdocTag of matchingJsdocTags) { + arrayHandler( + /** + * @type {import('@es-joy/jsdoccomment').JsdocTagWithInline} + */ ( + matchingJsdocTag + ), targetTagName, + ); + } +}; + /** * Get all tags, inline tags and inline tags in tags * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc @@ -1652,19 +1809,24 @@ const getRegexFromString = (regexString, requiredFlags) => { return new RegExp(regex, flags); }; -export default { +export { comparePaths, dropPathSegmentQuotes, enforcedContexts, exemptSpeciaMethods, + filterTags, flattenRoots, + forEachPreferredTag, getAllTags, getContextObject, getFunctionParameterNames, getIndent, getJsdocTagsDeep, getPreferredTagName, + getPreferredTagNameSimple, getRegexFromString, + getTagDescription, + getTags, getTagsByType, getTagStructureForMode, hasATag, diff --git a/src/rules/convertToJsdocComments.js b/src/rules/convertToJsdocComments.js index 81f15b4da..30243daa4 100644 --- a/src/rules/convertToJsdocComments.js +++ b/src/rules/convertToJsdocComments.js @@ -2,7 +2,11 @@ import iterateJsdoc from '../iterateJsdoc.js'; import { getSettings, } from '../iterateJsdoc.js'; -import jsdocUtils from '../jsdocUtils.js'; +import { + getIndent, + getContextObject, + enforcedContexts, +} from '../jsdocUtils.js'; import { getNonJsdocComment, getDecorator, @@ -114,7 +118,7 @@ export default { ); } - const indent = jsdocUtils.getIndent({ + const indent = getIndent({ text: sourceCode.getText( /** @type {import('eslint').Rule.Node} */ (baseNode), /** @type {import('eslint').AST.SourceLocation} */ @@ -255,17 +259,17 @@ export default { // Todo: add contexts to check after (and handle if want both before and after) return { - ...jsdocUtils.getContextObject( - jsdocUtils.enforcedContexts(context, true, settings), + ...getContextObject( + enforcedContexts(context, true, settings), checkNonJsdoc, ), - ...jsdocUtils.getContextObject( + ...getContextObject( contextsAfter, (_info, _handler, node) => { checkNonJsdocAfter(node, contextsAfter); }, ), - ...jsdocUtils.getContextObject( + ...getContextObject( contextsBeforeAndAfter, (_info, _handler, node) => { checkNonJsdoc({}, null, node); diff --git a/src/rules/requireJsdoc.js b/src/rules/requireJsdoc.js index 521f191a0..a6630e7a0 100644 --- a/src/rules/requireJsdoc.js +++ b/src/rules/requireJsdoc.js @@ -2,7 +2,15 @@ import exportParser from '../exportParser.js'; import { getSettings, } from '../iterateJsdoc.js'; -import jsdocUtils from '../jsdocUtils.js'; +import { + exemptSpeciaMethods, + isConstructor, + getFunctionParameterNames, + hasReturnValue, + getIndent, + getContextObject, + enforcedContexts, +} from '../jsdocUtils.js'; import { getDecorator, getJSDocComment, @@ -381,7 +389,7 @@ export default { // For those who have options configured against ANY constructors (or // setters or getters) being reported - if (jsdocUtils.exemptSpeciaMethods( + if (exemptSpeciaMethods( { description: '', inlineTags: [], @@ -405,10 +413,10 @@ export default { // Avoid reporting param-less, return-less constructor methods (when // `exemptEmptyConstructors` option is set) - exemptEmptyConstructors && jsdocUtils.isConstructor(node) + exemptEmptyConstructors && isConstructor(node) ) { - const functionParameterNames = jsdocUtils.getFunctionParameterNames(node); - if (!functionParameterNames.length && !jsdocUtils.hasReturnValue(node)) { + const functionParameterNames = getFunctionParameterNames(node); + if (!functionParameterNames.length && !hasReturnValue(node)) { return; } } @@ -427,7 +435,7 @@ export default { baseNode = decorator; } - const indent = jsdocUtils.getIndent({ + const indent = getIndent({ text: sourceCode.getText( /** @type {import('eslint').Rule.Node} */ (baseNode), /** @type {import('eslint').AST.SourceLocation} */ @@ -516,8 +524,8 @@ export default { }; return { - ...jsdocUtils.getContextObject( - jsdocUtils.enforcedContexts(context, [], settings), + ...getContextObject( + enforcedContexts(context, [], settings), checkJsDoc, ), ArrowFunctionExpression (node) { diff --git a/test/jsdocUtils.js b/test/jsdocUtils.js index 2de63cb14..9152ac9ec 100644 --- a/test/jsdocUtils.js +++ b/test/jsdocUtils.js @@ -1,4 +1,4 @@ -import jsdocUtils from '../src/jsdocUtils.js'; +import * as jsdocUtils from '../src/jsdocUtils.js'; import { expect, } from 'chai'; @@ -8,26 +8,26 @@ import { */ describe('jsdocUtils', () => { - describe('getPreferredTagName()', () => { + describe('getPreferredTagNameSimple()', () => { context('no preferences', () => { context('alias name', () => { it('returns the primary tag name', () => { - expect(jsdocUtils.getPreferredTagName(/** @type {BadArgument} */ ({}), 'jsdoc', 'return')).to.equal('returns'); + expect(jsdocUtils.getPreferredTagNameSimple(/** @type {BadArgument} */ ({}), 'jsdoc', 'return')).to.equal('returns'); }); it('works with the constructor tag', () => { - expect(jsdocUtils.getPreferredTagName(/** @type {BadArgument} */ ({}), 'jsdoc', 'constructor')).to.equal('class'); + expect(jsdocUtils.getPreferredTagNameSimple(/** @type {BadArgument} */ ({}), 'jsdoc', 'constructor')).to.equal('class'); }); }); it('works with tags that clash with prototype properties', () => { - expect(jsdocUtils.getPreferredTagName(/** @type {BadArgument} */ ({}), 'jsdoc', 'toString')).to.equal('toString'); + expect(jsdocUtils.getPreferredTagNameSimple(/** @type {BadArgument} */ ({}), 'jsdoc', 'toString')).to.equal('toString'); }); it('returns the primary tag name', () => { - expect(jsdocUtils.getPreferredTagName(/** @type {BadArgument} */ ({}), 'jsdoc', 'returns')).to.equal('returns'); + expect(jsdocUtils.getPreferredTagNameSimple(/** @type {BadArgument} */ ({}), 'jsdoc', 'returns')).to.equal('returns'); }); }); context('with preferences', () => { it('returns the preferred tag name', () => { - expect(jsdocUtils.getPreferredTagName(/** @type {BadArgument} */ ({}), 'jsdoc', 'return', /** @type {BadArgument} */ ({ + expect(jsdocUtils.getPreferredTagNameSimple(/** @type {BadArgument} */ ({}), 'jsdoc', 'return', /** @type {BadArgument} */ ({ returns: 'return', }))).to.equal('return'); });