diff --git a/README.md b/README.md index 3dd86b67a..33d1ab669 100644 --- a/README.md +++ b/README.md @@ -8185,6 +8185,21 @@ function quux () { } // "jsdoc/no-missing-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock[postDelimiter=\"\"]:has(JsdocTypeUnion > JsdocTypeName[value=\"Bar\"]:nth-child(1))","context":"FunctionDeclaration","minimum":2}]}] // Message: Syntax is required: FunctionDeclaration with JsdocBlock[postDelimiter=""]:has(JsdocTypeUnion > JsdocTypeName[value="Bar"]:nth-child(1)) + +/** + * @param ab + * @param cd + */ +// "jsdoc/no-missing-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Require names matching `/^opt_/i`."}]}] +// Message: Require names matching `/^opt_/i`. + +/** + * @param ab + * @param cd + */ +function quux () {} +// "jsdoc/no-missing-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Require names matching `/^opt_/i`."}]}] +// Message: Require names matching `/^opt_/i`. ```` The following patterns are not considered problems: @@ -8219,6 +8234,19 @@ function baz () { } // "jsdoc/no-missing-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock[postDelimiter=\"\"]:has(JsdocTypeUnion > JsdocTypeName[value=\"Bar\"]:nth-child(1))","context":"FunctionDeclaration","minimum":2}]}] + +/** + * @param opt_a + * @param opt_b + */ +// "jsdoc/no-missing-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Require names matching `/^opt_/i`."}]}] + +/** + * @param opt_a + * @param opt_b + */ +function quux () {} +// "jsdoc/no-missing-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Require names matching `/^opt_/i`."}]}] ```` @@ -8513,6 +8541,29 @@ function quux () { function a () {} // "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"FunctionDeclaration","message":"Only allowing names not matching `/^opt_/i`."}]}] // Message: Only allowing names not matching `/^opt_/i`. + +/** + * @param opt_a + * @param opt_b + */ +function a () {} +// "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Only allowing names not matching `/^opt_/i`."}]}] +// Message: Only allowing names not matching `/^opt_/i`. + +/** + * @param opt_a + * @param opt_b + */ +function a () {} +// "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/not-this/])","context":"any","message":"Only allowing names not matching `/^not-this/i`."},{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Only allowing names not matching `/^opt_/i`."}]}] +// Message: Only allowing names not matching `/^opt_/i`. + +/** + * @param opt_a + * @param opt_b + */ +// "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Only allowing names not matching `/^opt_/i`."}]}] +// Message: Only allowing names not matching `/^opt_/i`. ```` The following patterns are not considered problems: @@ -8533,6 +8584,19 @@ function quux () { } // "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock[postDelimiter=\"\"]:has(JsdocTypeUnion > JsdocTypeName[value=\"Foo\"]:nth-child(1))","context":"FunctionDeclaration"}]}] + +/** + * @param ab + * @param cd + */ +function a () {} +// "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Only allowing names not matching `/^opt_/i`."}]}] + +/** + * @param ab + * @param cd + */ +// "jsdoc/no-restricted-syntax": ["error"|"warn", {"contexts":[{"comment":"JsdocBlock:has(JsdocTag[name=/opt_/])","context":"any","message":"Only allowing names not matching `/^opt_/i`."}]}] ```` diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js index a572e205c..2c4507c74 100644 --- a/src/iterateJsdoc.js +++ b/src/iterateJsdoc.js @@ -681,7 +681,7 @@ const iterate = ( info, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, - sourceCode, iterator, state, iteratingAll, + sourceCode, iterator, state, lastComment, iteratingAll, ) => { const report = makeReport(context, jsdocNode); @@ -745,10 +745,12 @@ const getIndentAndJSDoc = function (lines, jsdocNode) { * * @param {JsdocVisitor} iterator * @param {{meta: any}} ruleConfig + * @param contexts */ -const iterateAllJsdocs = (iterator, ruleConfig) => { +const iterateAllJsdocs = (iterator, ruleConfig, contexts) => { const trackedJsdocs = []; + let handler; let settings; const callIterator = (context, node, jsdocNodes, state, lastCall) => { const sourceCode = context.getSourceCode(); @@ -764,12 +766,21 @@ const iterateAllJsdocs = (iterator, ruleConfig) => { lines, jsdocNode, ); + let lastComment; + if (contexts && contexts.every(({comment}) => { + lastComment = comment; + + return handler(comment, jsdoc) === false; + })) { + return; + } + iterate( - null, + lastComment ? {comment: lastComment, selector: node?.type} : null, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, - state, true, + state, lastComment, true, ); }); if (lastCall && ruleConfig.exit) { @@ -788,6 +799,9 @@ const iterateAllJsdocs = (iterator, ruleConfig) => { if (!settings) { return {}; } + if (contexts) { + handler = commentHandler(settings); + } const state = {}; @@ -799,11 +813,11 @@ const iterateAllJsdocs = (iterator, ruleConfig) => { return; } - const comment = getJSDocComment(sourceCode, node, settings); - if (trackedJsdocs.includes(comment)) { + const commentNode = getJSDocComment(sourceCode, node, settings); + if (trackedJsdocs.includes(commentNode)) { return; } - if (!comment) { + if (!commentNode) { if (ruleConfig.nonComment) { ruleConfig.nonComment({ node, @@ -814,8 +828,8 @@ const iterateAllJsdocs = (iterator, ruleConfig) => { return; } - trackedJsdocs.push(comment); - callIterator(context, node, [comment], state); + trackedJsdocs.push(commentNode); + callIterator(context, node, [commentNode], state); }, 'Program:exit' () { const allComments = sourceCode.getAllComments(); @@ -911,18 +925,25 @@ export default function iterateJsdoc (iterator, ruleConfig) { * a list with parser callback function. */ create (context) { + const settings = getSettings(context); + if (!settings) { + return {}; + } + let contexts; if (ruleConfig.contextDefaults || ruleConfig.contextSelected) { contexts = jsdocUtils.enforcedContexts(context, ruleConfig.contextDefaults); - if (contexts?.includes('any')) { - return iterateAllJsdocs(iterator, ruleConfig).create(context); + const hasPlainAny = contexts?.includes('any'); + const hasObjectAny = !hasPlainAny && contexts?.find((ctxt) => { + return ctxt?.context === 'any'; + }); + if (hasPlainAny || hasObjectAny) { + return iterateAllJsdocs( + iterator, ruleConfig, hasObjectAny ? contexts : null, + ).create(context); } } const sourceCode = context.getSourceCode(); - const settings = getSettings(context); - if (!settings) { - return {}; - } const {lines} = sourceCode; const state = {}; diff --git a/src/rules/noMissingSyntax.js b/src/rules/noMissingSyntax.js index 807255e57..5dbde0ad6 100644 --- a/src/rules/noMissingSyntax.js +++ b/src/rules/noMissingSyntax.js @@ -52,9 +52,15 @@ export default iterateJsdoc(({ const contextStr = typeof cntxt === 'object' ? cntxt.context : cntxt; const comment = cntxt?.comment ?? ''; - if (!state.selectorMap[contextStr] || - !state.selectorMap[contextStr][comment] || - state.selectorMap[contextStr][comment] < (cntxt?.minimum ?? 1) + const contextKey = contextStr === 'any' ? undefined : contextStr; + + if ( + (!state.selectorMap[contextKey] || + !state.selectorMap[contextKey][comment] || + state.selectorMap[contextKey][comment] < (cntxt?.minimum ?? 1)) && + (contextStr !== 'any' || Object.values(state.selectorMap).every((cmmnt) => { + return !cmmnt[comment] || cmmnt[comment] < (cntxt?.minimum ?? 1); + })) ) { const message = cntxt?.message ?? 'Syntax is required: {{context}}' + (comment ? ' with {{comment}}' : ''); diff --git a/src/rules/noRestrictedSyntax.js b/src/rules/noRestrictedSyntax.js index 8034f972e..b093601b7 100644 --- a/src/rules/noRestrictedSyntax.js +++ b/src/rules/noRestrictedSyntax.js @@ -14,7 +14,8 @@ export default iterateJsdoc(({ const foundContext = contexts.find((cntxt) => { return cntxt === selector || - typeof cntxt === 'object' && selector === cntxt.context && + typeof cntxt === 'object' && + (cntxt.context === 'any' || selector === cntxt.context) && comment === cntxt.comment; }); diff --git a/test/rules/assertions/noMissingSyntax.js b/test/rules/assertions/noMissingSyntax.js index 2933c8cf5..6e3a6e6a5 100644 --- a/test/rules/assertions/noMissingSyntax.js +++ b/test/rules/assertions/noMissingSyntax.js @@ -115,6 +115,49 @@ export default { ], }], }, + { + code: ` + /** + * @param ab + * @param cd + */ + `, + errors: [{ + line: 1, + message: 'Require names matching `/^opt_/i`.', + }], + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Require names matching `/^opt_/i`.', + }, + ], + }], + }, + { + code: ` + /** + * @param ab + * @param cd + */ + function quux () {} + `, + errors: [{ + line: 1, + message: 'Require names matching `/^opt_/i`.', + }], + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Require names matching `/^opt_/i`.', + }, + ], + }], + }, ], valid: [ { @@ -168,5 +211,40 @@ export default { ], }], }, + { + code: ` + /** + * @param opt_a + * @param opt_b + */ + `, + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Require names matching `/^opt_/i`.', + }, + ], + }], + }, + { + code: ` + /** + * @param opt_a + * @param opt_b + */ + function quux () {} + `, + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Require names matching `/^opt_/i`.', + }, + ], + }], + }, ], }; diff --git a/test/rules/assertions/noRestrictedSyntax.js b/test/rules/assertions/noRestrictedSyntax.js index e0d8c8c6e..3d613215e 100644 --- a/test/rules/assertions/noRestrictedSyntax.js +++ b/test/rules/assertions/noRestrictedSyntax.js @@ -137,6 +137,76 @@ export default { ], }], }, + { + code: ` + /** + * @param opt_a + * @param opt_b + */ + function a () {} + `, + errors: [{ + line: 2, + message: 'Only allowing names not matching `/^opt_/i`.', + }], + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Only allowing names not matching `/^opt_/i`.', + }, + ], + }], + }, + { + code: ` + /** + * @param opt_a + * @param opt_b + */ + function a () {} + `, + errors: [{ + line: 2, + message: 'Only allowing names not matching `/^opt_/i`.', + }], + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/not-this/])', + context: 'any', + message: 'Only allowing names not matching `/^not-this/i`.', + }, + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Only allowing names not matching `/^opt_/i`.', + }, + ], + }], + }, + { + code: ` + /** + * @param opt_a + * @param opt_b + */ + `, + errors: [{ + line: 2, + message: 'Only allowing names not matching `/^opt_/i`.', + }], + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Only allowing names not matching `/^opt_/i`.', + }, + ], + }], + }, ], valid: [ { @@ -172,5 +242,40 @@ export default { ], }], }, + { + code: ` + /** + * @param ab + * @param cd + */ + function a () {} + `, + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Only allowing names not matching `/^opt_/i`.', + }, + ], + }], + }, + { + code: ` + /** + * @param ab + * @param cd + */ + `, + options: [{ + contexts: [ + { + comment: 'JsdocBlock:has(JsdocTag[name=/opt_/])', + context: 'any', + message: 'Only allowing names not matching `/^opt_/i`.', + }, + ], + }], + }, ], };