From 0f27282c01cb5b0a8f8d3cf2936cabc35dd4d4ed Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Mon, 28 Mar 2022 08:43:49 +0800 Subject: [PATCH] feat: unless the user supplies their own `object` type `preferredTypes`, prefer `object` for plain objects and otherwise prefer `Object<>`; fixes #800 (#855) --- .README/rules/check-types.md | 7 +- README.md | 31 +++++++-- src/rules/checkTypes.js | 104 ++++++++++++++++------------ test/rules/assertions/checkTypes.js | 72 +++++++++++++++++-- 4 files changed, 157 insertions(+), 57 deletions(-) diff --git a/.README/rules/check-types.md b/.README/rules/check-types.md index 4e8145be1..65475b110 100644 --- a/.README/rules/check-types.md +++ b/.README/rules/check-types.md @@ -107,11 +107,12 @@ as a lone type. However, one additional complexity is that TypeScript allows and actually [currently requires](https://github.com/microsoft/TypeScript/issues/20555) `Object` (with the initial upper-case) if used in the syntax `Object.` or `Object` or `Object} foo */ function b () {} // Settings: {"jsdoc":{"mode":"typescript","preferredTypes":{"object":"Object"}}} @@ -5557,6 +5558,24 @@ function quux (foo) { } // Settings: {"jsdoc":{"mode":"typescript","preferredTypes":{"Object":"object","object.<>":"Object<>","Object.<>":"Object<>","object<>":"Object<>"}}} // Message: Invalid JSDoc @param "foo" type "object"; prefer: "Object<>". + +/** + * @param {object.} foo + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"mode":"typescript","preferredTypes":{"Object":"object","object.<>":"Object<>","Object.<>":"Object<>","object<>":"Object<>"}}} +// Message: Invalid JSDoc @param "foo" type "object"; prefer: "Object<>". + +/** + * @param {object.} foo + */ +function quux (foo) { + +} +// Settings: {"jsdoc":{"mode":"typescript"}} +// Message: Invalid JSDoc @param "foo" type "object"; prefer: "Object<>". ```` The following patterns are not considered problems: @@ -5817,7 +5836,7 @@ function b () {} function a () {} /** - * @typedef {Object} foo + * @typedef {Object} foo */ function b () {} // Settings: {"jsdoc":{"mode":"typescript"}} @@ -5846,7 +5865,7 @@ function quux (foo) { } /** - * @param {Object.} foo + * @param {Object} foo */ function quux (foo) { diff --git a/src/rules/checkTypes.js b/src/rules/checkTypes.js index 31e99c6d8..39aa9d42f 100644 --- a/src/rules/checkTypes.js +++ b/src/rules/checkTypes.js @@ -86,10 +86,27 @@ export default iterateJsdoc(({ }); const { - preferredTypes, + preferredTypes: preferredTypesOriginal, structuredTags, mode, } = settings; + + const injectObjectPreferredTypes = !('Object' in preferredTypesOriginal || + 'object' in preferredTypesOriginal || + 'object.<>' in preferredTypesOriginal || + 'Object.<>' in preferredTypesOriginal || + 'object<>' in preferredTypesOriginal); + + const preferredTypes = { + ...injectObjectPreferredTypes ? { + Object: 'object', + 'object.<>': 'Object<>', + 'Object.<>': 'Object<>', + 'object<>': 'Object<>', + } : {}, + ...preferredTypesOriginal, + }; + const { noDefaults, unifyParentAndChildTypeChecks, @@ -110,56 +127,55 @@ export default iterateJsdoc(({ let hasMatchingPreferredType = false; let isGenericMatch = false; let typeName = typeNodeName; - if (Object.keys(preferredTypes).length) { - const isNameOfGeneric = parentNode !== undefined && parentNode.type === 'JsdocTypeGeneric' && property === 'left'; - if (unifyParentAndChildTypeChecks || isNameOfGeneric) { - const brackets = parentNode?.meta?.brackets; - const dot = parentNode?.meta?.dot; - - if (brackets === 'angle') { - const checkPostFixes = dot ? [ - '.', '.<>', - ] : [ - '<>', - ]; - isGenericMatch = checkPostFixes.some((checkPostFix) => { - if (preferredTypes?.[typeNodeName + checkPostFix] !== undefined) { - typeName += checkPostFix; - - return true; - } - - return false; - }); - } - if (!isGenericMatch && property) { - const checkPostFixes = dot ? [ - '.', '.<>', - ] : [ - brackets === 'angle' ? '<>' : '[]', - ]; + const isNameOfGeneric = parentNode !== undefined && parentNode.type === 'JsdocTypeGeneric' && property === 'left'; + if (unifyParentAndChildTypeChecks || isNameOfGeneric) { + const brackets = parentNode?.meta?.brackets; + const dot = parentNode?.meta?.dot; + + if (brackets === 'angle') { + const checkPostFixes = dot ? [ + '.', '.<>', + ] : [ + '<>', + ]; + isGenericMatch = checkPostFixes.some((checkPostFix) => { + if (preferredTypes?.[typeNodeName + checkPostFix] !== undefined) { + typeName += checkPostFix; + + return true; + } + + return false; + }); + } - isGenericMatch = checkPostFixes.some((checkPostFix) => { - if (preferredTypes?.[checkPostFix] !== undefined) { - typeName = checkPostFix; + if (!isGenericMatch && property) { + const checkPostFixes = dot ? [ + '.', '.<>', + ] : [ + brackets === 'angle' ? '<>' : '[]', + ]; - return true; - } + isGenericMatch = checkPostFixes.some((checkPostFix) => { + if (preferredTypes?.[checkPostFix] !== undefined) { + typeName = checkPostFix; - return false; - }); - } + return true; + } + + return false; + }); } + } - const directNameMatch = preferredTypes?.[typeNodeName] !== undefined && - !Object.values(preferredTypes).includes(typeNodeName); - const unifiedSyntaxParentMatch = property && directNameMatch && unifyParentAndChildTypeChecks; - isGenericMatch = isGenericMatch || unifiedSyntaxParentMatch; + const directNameMatch = preferredTypes?.[typeNodeName] !== undefined && + !Object.values(preferredTypes).includes(typeNodeName); + const unifiedSyntaxParentMatch = property && directNameMatch && unifyParentAndChildTypeChecks; + isGenericMatch = isGenericMatch || unifiedSyntaxParentMatch; - hasMatchingPreferredType = isGenericMatch || - directNameMatch && !property; - } + hasMatchingPreferredType = isGenericMatch || + directNameMatch && !property; return [ hasMatchingPreferredType, typeName, isGenericMatch, diff --git a/test/rules/assertions/checkTypes.js b/test/rules/assertions/checkTypes.js index c6de12d0d..f0d1c031f 100644 --- a/test/rules/assertions/checkTypes.js +++ b/test/rules/assertions/checkTypes.js @@ -2048,7 +2048,7 @@ export default { function a () {} /** - * @typedef {Object} foo + * @typedef {Object} foo */ function b () {} `, @@ -2065,7 +2065,7 @@ export default { function a () {} /** - * @typedef {Object} foo + * @typedef {Object} foo */ function b () {} `, @@ -2292,6 +2292,70 @@ export default { }, }, }, + { + code: ` + /** + * @param {object.} foo + */ + function quux (foo) { + + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "object"; prefer: "Object<>".', + }, + ], + output: ` + /** + * @param {Object} foo + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + mode: 'typescript', + preferredTypes: { + Object: 'object', + 'object.<>': 'Object<>', + 'Object.<>': 'Object<>', + 'object<>': 'Object<>', + }, + }, + }, + }, + { + code: ` + /** + * @param {object.} foo + */ + function quux (foo) { + + } + `, + errors: [ + { + line: 3, + message: 'Invalid JSDoc @param "foo" type "object"; prefer: "Object<>".', + }, + ], + output: ` + /** + * @param {Object} foo + */ + function quux (foo) { + + } + `, + settings: { + jsdoc: { + mode: 'typescript', + }, + }, + }, ], valid: [ { @@ -2815,7 +2879,7 @@ export default { function a () {} /** - * @typedef {Object} foo + * @typedef {Object} foo */ function b () {} `, @@ -2891,7 +2955,7 @@ export default { { code: ` /** - * @param {Object.} foo + * @param {Object} foo */ function quux (foo) {