diff --git a/.README/README.md b/.README/README.md
index 9fbf83804..ca2890676 100644
--- a/.README/README.md
+++ b/.README/README.md
@@ -461,6 +461,34 @@ your files' code which are of interest to check. However, in
`eslint-plugin-jsdoc`, we also allow you to use these selectors to define
additional contexts where you wish our own rules to be applied.
+#### `contexts` format
+
+While at their simplest, these can be an array of string selectors, one can
+also supply an object with `context` (in place of the string) and one of two
+properties:
+
+1. For `require-jsdoc`, there is also a `inlineCommentBlock` property. See
+ that rule for details.
+2. For other rules, there is a `comment` property which adds to the `context`
+ in requiring that the `comment` AST condition is also met, e.g., to
+ require that certain tags are present and/or or types and type operators
+ are in use. Note that this AST has not been standardized and should be
+ considered experimental.
+ In addition to being generally useful for precision in specifying contexts,
+ it is hoped that the ability to specify required tags on structures can
+ be used for requiring `@type` or other types for a minimalist yet adequate
+ specification of types which can be used to compile JavaScript+JSDoc (JJ)
+ to WebAssembly (e.g., by converting it to TypeSscript and then using
+ AssemblyScript to convert to WebAssembly). (It may be possible that one
+ will need to require types with certain structures beyond function
+ declarations and the like, as well as optionally requiring specification
+ of number types.)
+
+#### Discovering available AST definitions
+
To know all of the AST definitions one may target, it will depend on the
[parser](https://eslint.org/docs/user-guide/configuring#specifying-parser)
you are using with ESLint (e.g., `espree` is the default parser for ESLint,
@@ -473,11 +501,13 @@ So you can look up a particular parser to see its rules, e.g., browse through
the [ESTree docs](https://github.com/estree/estree) as used by Espree or see
ESLint's [overview of the structure of AST](https://eslint.org/docs/developer-guide/working-with-custom-parsers#the-ast-specification).
-However, it can sometimes be even more helpful to get an idea of ASt by just
+However, it can sometimes be even more helpful to get an idea of AST by just
providing some of your JavaScript to the wonderful
[AST Explorer](https://astexplorer.net/) tool and see what AST is built out
of your code. You can set the tool to the specific parser which you are using.
+#### Uses/Tips for AST
+
And if you wish to introspect on the AST of code within your projects, you can
use [eslint-plugin-query](https://github.com/brettz9/eslint-plugin-query).
Though it also works as a plugin, you can use it with its own CLI, e.g.,
diff --git a/README.md b/README.md
index 3f2597744..bf4acd3dd 100644
--- a/README.md
+++ b/README.md
@@ -532,6 +532,36 @@ your files' code which are of interest to check. However, in
`eslint-plugin-jsdoc`, we also allow you to use these selectors to define
additional contexts where you wish our own rules to be applied.
+
+#### contexts
format
+
+While at their simplest, these can be an array of string selectors, one can
+also supply an object with `context` (in place of the string) and one of two
+properties:
+
+1. For `require-jsdoc`, there is also a `inlineCommentBlock` property. See
+ that rule for details.
+2. For other rules, there is a `comment` property which adds to the `context`
+ in requiring that the `comment` AST condition is also met, e.g., to
+ require that certain tags are present and/or or types and type operators
+ are in use. Note that this AST has not been standardized and should be
+ considered experimental.
+ In addition to being generally useful for precision in specifying contexts,
+ it is hoped that the ability to specify required tags on structures can
+ be used for requiring `@type` or other types for a minimalist yet adequate
+ specification of types which can be used to compile JavaScript+JSDoc (JJ)
+ to WebAssembly (e.g., by converting it to TypeSscript and then using
+ AssemblyScript to convert to WebAssembly). (It may be possible that one
+ will need to require types with certain structures beyond function
+ declarations and the like, as well as optionally requiring specification
+ of number types.)
+
+
+#### Discovering available AST definitions
+
To know all of the AST definitions one may target, it will depend on the
[parser](https://eslint.org/docs/user-guide/configuring#specifying-parser)
you are using with ESLint (e.g., `espree` is the default parser for ESLint,
@@ -544,11 +574,14 @@ So you can look up a particular parser to see its rules, e.g., browse through
the [ESTree docs](https://github.com/estree/estree) as used by Espree or see
ESLint's [overview of the structure of AST](https://eslint.org/docs/developer-guide/working-with-custom-parsers#the-ast-specification).
-However, it can sometimes be even more helpful to get an idea of ASt by just
+However, it can sometimes be even more helpful to get an idea of AST by just
providing some of your JavaScript to the wonderful
[AST Explorer](https://astexplorer.net/) tool and see what AST is built out
of your code. You can set the tool to the specific parser which you are using.
+
+#### Uses/Tips for AST
+
And if you wish to introspect on the AST of code within your projects, you can
use [eslint-plugin-query](https://github.com/brettz9/eslint-plugin-query).
Though it also works as a plugin, you can use it with its own CLI, e.g.,
@@ -9042,6 +9075,16 @@ class Foo {
}
// "jsdoc/require-description": ["error"|"warn", {"checkConstructors":false,"contexts":["MethodDefinition"]}]
// Message: Missing JSDoc block description.
+
+/**
+ * @class
+ * @implements {Bar}
+ */
+class quux {
+
+}
+// "jsdoc/require-description": ["error"|"warn", {"contexts":[{"comment":"JSDocBlock[postDelimiter=\"\"]:has(JSDocTag)","context":"ClassDeclaration"}],"descriptionStyle":"tag"}]
+// Message: Missing JSDoc @description declaration.
````
The following patterns are not considered problems:
diff --git a/package.json b/package.json
index 054c45695..4e0eea810 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"@es-joy/jsdoccomment": "^0.1.1",
"comment-parser": "1.1.5",
"debug": "^4.3.1",
+ "esquery": "^1.4.0",
"jsdoctypeparser": "^9.0.0",
"lodash": "^4.17.21",
"regextras": "^0.7.1",
diff --git a/src/iterateJsdoc.js b/src/iterateJsdoc.js
index a5e81fcb5..5751c5b73 100644
--- a/src/iterateJsdoc.js
+++ b/src/iterateJsdoc.js
@@ -17,6 +17,10 @@ import {
seedBlock,
seedTokens,
} from 'comment-parser/lib/util';
+import esquery from 'esquery';
+import {
+ parse as jsdoctypeParse,
+} from 'jsdoctypeparser';
import _ from 'lodash';
import jsdocUtils from './jsdocUtils';
@@ -247,16 +251,15 @@ const getUtils = (
utils.getDescription = () => {
const descriptions = [];
let lastDescriptionLine;
- if (jsdoc.source[0].tokens.description) {
- descriptions.push(jsdoc.source[0].tokens.description);
- }
- jsdoc.source.slice(1).some(({tokens: {description, tag, end}}, idx) => {
- if (tag || end) {
- lastDescriptionLine = idx;
+ jsdoc.source.some(({tokens: {description, tag, end}}, idx) => {
+ if (idx && (tag || end)) {
+ lastDescriptionLine = idx - 1;
return true;
}
- descriptions.push(description);
+ if (idx || description) {
+ descriptions.push(description);
+ }
return false;
});
@@ -705,12 +708,10 @@ const makeReport = (context, commentNode) => {
*/
const iterate = (
+ indent, jsdoc,
ruleConfig, context, lines, jsdocNode, node, settings,
sourceCode, iterator, state, iteratingAll,
) => {
- const sourceLine = lines[jsdocNode.loc.start.line - 1];
- const indent = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
- const jsdoc = parseComment(jsdocNode, indent);
const report = makeReport(context, jsdocNode);
const utils = getUtils(
@@ -758,6 +759,14 @@ const iterate = (
});
};
+const getIndentAndJSDoc = function (lines, jsdocNode) {
+ const sourceLine = lines[jsdocNode.loc.start.line - 1];
+ const indnt = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
+ const jsdc = parseComment(jsdocNode, indnt);
+
+ return [indnt, jsdc];
+};
+
/**
* Create an eslint rule that iterates over all JSDocs, regardless of whether
* they are attached to a function-like node.
@@ -778,7 +787,13 @@ const iterateAllJsdocs = (iterator, ruleConfig) => {
if (!(/^\/\*\*\s/).test(sourceCode.getText(jsdocNode))) {
return;
}
+
+ const [indent, jsdoc] = getIndentAndJSDoc(
+ lines, jsdocNode,
+ );
+
iterate(
+ indent, jsdoc,
ruleConfig, context, lines, jsdocNode, node,
settings, sourceCode, iterator,
state, true,
@@ -886,6 +901,14 @@ export {
parseComment,
};
+const toCamelCase = (str) => {
+ return str.toLowerCase().replace(/^[a-z]/, (init) => {
+ return init.toUpperCase();
+ }).replace(/_([a-z])/, (__, wordInit) => {
+ return wordInit.toUpperCase();
+ });
+};
+
/**
* @param {JsdocVisitor} iterator
* @param {{
@@ -934,29 +957,255 @@ export default function iterateJsdoc (iterator, ruleConfig) {
if (!settings) {
return {};
}
+ const {mode} = settings;
const {lines} = sourceCode;
- const checkJsdoc = (node) => {
+ const checkJsdoc = (info, handler, node) => {
const jsdocNode = getJSDocComment(sourceCode, node, settings);
if (!jsdocNode) {
return;
}
+ const [indent, jsdoc] = getIndentAndJSDoc(
+ lines, jsdocNode,
+ );
+
+ if (
+ // Note, `handler` should already be bound in its first argument
+ // with these only to be called after the value of
+ // `comment`
+ handler && handler(jsdoc) === false
+ ) {
+ return;
+ }
+
iterate(
+ indent, jsdoc,
ruleConfig, context, lines, jsdocNode, node,
settings, sourceCode, iterator,
);
};
if (ruleConfig.contextDefaults) {
- return jsdocUtils.getContextObject(contexts, checkJsdoc);
+ return jsdocUtils.getContextObject(
+ contexts,
+ checkJsdoc,
+ (commentSelector, jsdoc) => {
+ const selector = esquery.parse(commentSelector);
+
+ const {tokens: {
+ delimiter: delimiterRoot,
+ postDelimiter: postDelimiterRoot,
+ end: endRoot,
+ description: descriptionRoot,
+ }} = jsdoc.source[0];
+ const obj = {
+ delimiter: delimiterRoot,
+ description: descriptionRoot,
+
+ // This will be overwritten if there are other entries
+ end: endRoot,
+
+ postDelimiter: postDelimiterRoot,
+ };
+ const ast = {
+ type: 'JSDocBlock',
+ ...obj,
+ };
+
+ const tags = [];
+ let lastDescriptionLine;
+ let lastTagDescriptionHolder = false;
+ let lastTagTypeHolder = false;
+ jsdoc.source.slice(1).forEach((info, idx) => {
+ const {tokens} = info;
+ if (tokens.tag || tokens.end) {
+ if (lastDescriptionLine === undefined) {
+ lastDescriptionLine = idx;
+ }
+ if (tokens.end) {
+ ast.end = tokens.end;
+ } else {
+ const {
+ // eslint-disable-next-line no-unused-vars -- Discarding
+ end: ed,
+ ...tkns
+ } = tokens;
+
+ let parsedType = null;
+ try {
+ parsedType = jsdoctypeParse(tokens.type, {mode});
+ } catch {
+ // Ignore
+ }
+
+ // Todo: See about getting jsdoctypeparser to make these
+ // changes; the AST might also be rethought to use
+ // fewer types and more properties
+ const sel = esquery.parse('*[type]');
+ esquery.traverse(parsedType, sel, (node) => {
+ const {type} = node;
+
+ node.type = `JSDocType${toCamelCase(type)}`;
+ });
+
+ const tag = {
+ ...tkns,
+ descriptionLines: [],
+ parsedType,
+ rawType: tokens.type,
+ type: 'JSDocTag',
+ typeLines: [],
+ };
+ if (!lastTagDescriptionHolder) {
+ const {
+ delimiter,
+ description,
+ postDelimiter,
+ start,
+ } = tkns;
+ tag.descriptionLines = lastTagDescriptionHolder = [
+ {
+ delimiter,
+ description,
+ postDelimiter,
+ start,
+ type: 'JSDocDescriptionLine',
+ },
+ ];
+ }
+ if (!lastTagTypeHolder) {
+ const {
+ delimiter,
+ type: rawType,
+ postDelimiter,
+ start,
+ } = tkns;
+ tag.typeLines = lastTagTypeHolder = [
+ {
+ delimiter,
+ postDelimiter,
+ rawType,
+ start,
+ type: 'JSDocTypeLine',
+ },
+ ];
+ }
+ tags.push(tag);
+ }
+
+ return;
+
+ // Multi-line tag descriptions
+ }
+
+ if (lastTagDescriptionHolder && tokens.description) {
+ const {
+ delimiter,
+ description,
+ postDelimiter,
+ start,
+ } = tokens;
+ lastTagDescriptionHolder.push(
+ {
+ delimiter,
+ description,
+ postDelimiter,
+ start,
+ type: 'JSDocDescriptionLine',
+ },
+ );
+
+ return;
+ }
+ if (lastTagTypeHolder && tokens.type) {
+ const {
+ delimiter,
+ postDelimiter,
+ start,
+ type: rawType,
+ } = tokens;
+ lastTagDescriptionHolder.push(
+ {
+ delimiter,
+ postDelimiter,
+ rawType,
+ start,
+ type: 'JSDocTypeLine',
+ },
+ );
+
+ return;
+ }
+ ast.description += '\n' + tokens.description;
+ });
+
+ ast.lastDescriptionLine = lastDescriptionLine;
+ ast.tags = tags;
+
+ console.log('jsdoc', jsdoc);
+ console.log('ast', ast);
+
+ /* eslint-disable sort-keys-fix/sort-keys-fix -- Keep in order */
+ const typeVisitorKeys = {
+ NAME: [],
+ NAMED_PARAMETER: ['typeName'],
+ MEMBER: ['owner'],
+ UNION: ['left', 'right'],
+ INTERSECTION: ['left', 'right'],
+ VARIADIC: ['value'],
+ RECORD: ['entries'],
+ RECORD_ENTRY: ['value'],
+ TUPLE: ['entries'],
+ GENERIC: ['subject', 'objects'],
+ MODULE: ['value'],
+ OPTIONAL: ['value'],
+ NULLABLE: ['value'],
+ NOT_NULLABLE: ['value'],
+ FUNCTION: ['params', 'returns', 'this', 'new'],
+ ARROW: ['params', 'returns'],
+ ANY: [],
+ UNKNOWN: [],
+ INNER_MEMBER: ['owner'],
+ INSTANCE_MEMBER: ['owner'],
+ STRING_VALUE: [],
+ NUMBER_VALUE: [],
+ EXTERNAL: [],
+ FILE_PATH: [],
+ PARENTHESIS: ['value'],
+ TYPE_QUERY: ['name'],
+ KEY_QUERY: ['value'],
+ IMPORT: ['path'],
+ /* eslint-enable sort-keys-fix/sort-keys-fix -- Keep in order */
+ };
+
+ const convertedTypeVisitorKeys = Object.entries(
+ typeVisitorKeys,
+ ).reduce((object, [key, value]) => {
+ object[`JSDocType${toCamelCase(key)}`] = value;
+
+ return object;
+ }, {});
+
+ return esquery.matches(ast, selector, null, {
+ visitorKeys: {
+ ...convertedTypeVisitorKeys,
+ JSDocBlock: ['tags'],
+ JSDocDescriptionLine: [],
+ JSDocTag: ['descriptionLines', 'typeLines', 'parsedType'],
+ },
+ });
+ },
+ );
}
+ const checkJsdocNoHandler = checkJsdoc.bind(null, null, null);
+
return {
- ArrowFunctionExpression: checkJsdoc,
- FunctionDeclaration: checkJsdoc,
- FunctionExpression: checkJsdoc,
+ ArrowFunctionExpression: checkJsdocNoHandler,
+ FunctionDeclaration: checkJsdocNoHandler,
+ FunctionExpression: checkJsdocNoHandler,
};
},
meta: ruleConfig.meta,
diff --git a/src/jsdocUtils.js b/src/jsdocUtils.js
index 6a92d9939..1d8acc7b3 100644
--- a/src/jsdocUtils.js
+++ b/src/jsdocUtils.js
@@ -1084,15 +1084,22 @@ const enforcedContexts = (context, defaultContexts) => {
/**
* @param {string[]} contexts
* @param {Function} checkJsdoc
+ * @param {Function} handler
*/
-const getContextObject = (contexts, checkJsdoc) => {
+const getContextObject = (contexts, checkJsdoc, handler) => {
const properties = {};
contexts.forEach((prop) => {
if (typeof prop === 'object') {
- properties[prop.context] = checkJsdoc;
+ if (prop.comment) {
+ properties[prop.context] = checkJsdoc.bind(
+ null, null, handler.bind(null, prop.comment),
+ );
+ } else {
+ properties[prop.context] = checkJsdoc.bind(null, null, null);
+ }
} else {
- properties[prop] = checkJsdoc;
+ properties[prop] = checkJsdoc.bind(null, null, null);
}
});
diff --git a/src/rules/implementsOnClasses.js b/src/rules/implementsOnClasses.js
index d6c2cbdd0..7a794aee0 100644
--- a/src/rules/implementsOnClasses.js
+++ b/src/rules/implementsOnClasses.js
@@ -35,7 +35,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/matchDescription.js b/src/rules/matchDescription.js
index e897db8f9..91f93ae07 100644
--- a/src/rules/matchDescription.js
+++ b/src/rules/matchDescription.js
@@ -93,7 +93,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/noDefaults.js b/src/rules/noDefaults.js
index 3ae125472..7c4c15c22 100644
--- a/src/rules/noDefaults.js
+++ b/src/rules/noDefaults.js
@@ -46,7 +46,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/noTypes.js b/src/rules/noTypes.js
index 3d5d763f4..a280f2120 100644
--- a/src/rules/noTypes.js
+++ b/src/rules/noTypes.js
@@ -35,7 +35,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireDescription.js b/src/rules/requireDescription.js
index 3dc20b6d0..93b9947db 100644
--- a/src/rules/requireDescription.js
+++ b/src/rules/requireDescription.js
@@ -99,7 +99,23 @@ export default iterateJsdoc(({
},
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireExample.js b/src/rules/requireExample.js
index c328d6da8..86d5bf2e4 100644
--- a/src/rules/requireExample.js
+++ b/src/rules/requireExample.js
@@ -68,7 +68,23 @@ export default iterateJsdoc(({
},
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireJsdoc.js b/src/rules/requireJsdoc.js
index 45d628780..7a4a253d9 100644
--- a/src/rules/requireJsdoc.js
+++ b/src/rules/requireJsdoc.js
@@ -196,14 +196,15 @@ export default {
publicOnly, exemptEmptyFunctions, exemptEmptyConstructors, enableFixer,
} = getOptions(context);
- const checkJsDoc = (node, isFunctionContext) => {
+ const checkJsDoc = (isFunctionContext, handler, node) => {
const jsDocNode = getJSDocComment(sourceCode, node, settings);
if (jsDocNode) {
return;
}
- // For those who have options configured against ANY constructors (or setters or getters) being reported
+ // For those who have options configured against ANY constructors (or
+ // setters or getters) being reported
if (jsdocUtils.exemptSpeciaMethods(
{tags: []}, node, context, [OPTIONS_SCHEMA],
)) {
@@ -211,10 +212,12 @@ export default {
}
if (
- // Avoid reporting param-less, return-less functions (when `exemptEmptyFunctions` option is set)
+ // Avoid reporting param-less, return-less functions (when
+ // `exemptEmptyFunctions` option is set)
exemptEmptyFunctions && isFunctionContext ||
- // Avoid reporting param-less, return-less constructor methods (when `exemptEmptyConstructors` option is set)
+ // Avoid reporting param-less, return-less constructor methods (when
+ // `exemptEmptyConstructors` option is set)
exemptEmptyConstructors && jsdocUtils.isConstructor(node)
) {
const functionParameterNames = jsdocUtils.getFunctionParameterNames(node);
@@ -288,7 +291,10 @@ export default {
};
return {
- ...jsdocUtils.getContextObject(jsdocUtils.enforcedContexts(context, []), checkJsDoc),
+ ...jsdocUtils.getContextObject(
+ jsdocUtils.enforcedContexts(context, []),
+ checkJsDoc,
+ ),
ArrowFunctionExpression (node) {
if (!hasOption('ArrowFunctionExpression')) {
return;
@@ -298,7 +304,7 @@ export default {
['VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration'].includes(node.parent.type) ||
['Property', 'ObjectProperty', 'ClassProperty'].includes(node.parent.type) && node === node.parent.value
) {
- checkJsDoc(node, true);
+ checkJsDoc(true, null, node);
}
},
@@ -307,7 +313,7 @@ export default {
return;
}
- checkJsDoc(node);
+ checkJsDoc(false, null, node);
},
ClassExpression (node) {
@@ -315,7 +321,7 @@ export default {
return;
}
- checkJsDoc(node);
+ checkJsDoc(false, null, node);
},
FunctionDeclaration (node) {
@@ -323,12 +329,12 @@ export default {
return;
}
- checkJsDoc(node, true);
+ checkJsDoc(true, null, node);
},
FunctionExpression (node) {
if (hasOption('MethodDefinition') && node.parent.type === 'MethodDefinition') {
- checkJsDoc(node, true);
+ checkJsDoc(true, null, node);
return;
}
@@ -341,7 +347,7 @@ export default {
['VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration'].includes(node.parent.type) ||
['Property', 'ObjectProperty', 'ClassProperty'].includes(node.parent.type) && node === node.parent.value
) {
- checkJsDoc(node, true);
+ checkJsDoc(true, null, node);
}
},
};
diff --git a/src/rules/requireParam.js b/src/rules/requireParam.js
index 5cba97c15..7b76d0656 100644
--- a/src/rules/requireParam.js
+++ b/src/rules/requireParam.js
@@ -345,7 +345,23 @@ export default iterateJsdoc(({
},
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireParamDescription.js b/src/rules/requireParamDescription.js
index 0cd154c96..37319f8f5 100644
--- a/src/rules/requireParamDescription.js
+++ b/src/rules/requireParamDescription.js
@@ -26,7 +26,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireParamName.js b/src/rules/requireParamName.js
index 0bf05b2bf..1d1c32397 100644
--- a/src/rules/requireParamName.js
+++ b/src/rules/requireParamName.js
@@ -26,7 +26,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireParamType.js b/src/rules/requireParamType.js
index 4bd58250d..78b583bd8 100644
--- a/src/rules/requireParamType.js
+++ b/src/rules/requireParamType.js
@@ -26,7 +26,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireReturns.js b/src/rules/requireReturns.js
index e9558d20c..11d8dcb59 100644
--- a/src/rules/requireReturns.js
+++ b/src/rules/requireReturns.js
@@ -114,7 +114,23 @@ export default iterateJsdoc(({
},
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireReturnsDescription.js b/src/rules/requireReturnsDescription.js
index 6e4116389..af7787918 100644
--- a/src/rules/requireReturnsDescription.js
+++ b/src/rules/requireReturnsDescription.js
@@ -28,7 +28,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireReturnsType.js b/src/rules/requireReturnsType.js
index 8985c2d6c..ef443f968 100644
--- a/src/rules/requireReturnsType.js
+++ b/src/rules/requireReturnsType.js
@@ -22,7 +22,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireThrows.js b/src/rules/requireThrows.js
index 9cd2db6bc..ffda0dfdc 100644
--- a/src/rules/requireThrows.js
+++ b/src/rules/requireThrows.js
@@ -70,7 +70,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireYields.js b/src/rules/requireYields.js
index eb22096e3..adfe7d224 100644
--- a/src/rules/requireYields.js
+++ b/src/rules/requireYields.js
@@ -146,7 +146,23 @@ export default iterateJsdoc(({
properties: {
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/src/rules/requireYieldsCheck.js b/src/rules/requireYieldsCheck.js
index 9ecc355f2..0b56dfdc4 100644
--- a/src/rules/requireYieldsCheck.js
+++ b/src/rules/requireYieldsCheck.js
@@ -118,7 +118,23 @@ export default iterateJsdoc(({
},
contexts: {
items: {
- type: 'string',
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ additionalProperties: false,
+ properties: {
+ comment: {
+ type: 'string',
+ },
+ context: {
+ type: 'string',
+ },
+ },
+ type: 'object',
+ },
+ ],
},
type: 'array',
},
diff --git a/test/rules/assertions/requireDescription.js b/test/rules/assertions/requireDescription.js
index 4ce6d0b63..eb6fdcf87 100644
--- a/test/rules/assertions/requireDescription.js
+++ b/test/rules/assertions/requireDescription.js
@@ -516,6 +516,34 @@ export default {
},
],
},
+ {
+ code: `
+ /**
+ * @class
+ * @implements {Bar}
+ */
+ class quux {
+
+ }
+ `,
+ errors: [
+ {
+ message: 'Missing JSDoc @description declaration.',
+ },
+ ],
+ options: [
+ {
+ contexts: [
+ {
+ // comment: 'JSDocBlock[whitespace=/\\s{4}/] > JSDocTag[name="class"]',
+ comment: 'JSDocBlock[postDelimiter=""]:has(JSDocTag)',
+ context: 'ClassDeclaration',
+ },
+ ],
+ descriptionStyle: 'tag',
+ },
+ ],
+ },
],
valid: [
{