Skip to content

Commit

Permalink
feat: unless the user supplies their own object type `preferredType…
Browse files Browse the repository at this point in the history
…s`, prefer `object` for plain objects and otherwise prefer `Object<>`; fixes #800 (#855)
  • Loading branch information
brettz9 committed Mar 28, 2022
1 parent a530862 commit 0f27282
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 57 deletions.
7 changes: 4 additions & 3 deletions .README/rules/check-types.md
Expand Up @@ -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.<keyType, valueType>` or `Object<keyType, valueType`, perhaps to
adhere to what [JSDoc documents](https://jsdoc.app/tags-type.html).
adhere to that which [JSDoc documents](https://jsdoc.app/tags-type.html).

So, for optimal compatibility with TypeScript (especially since TypeScript
tools can be used on plain JavaScript with JSDoc), we are now allowing this
TypeScript approach by default.
tools can be used on plain JavaScript with JSDoc), we are now requiring this
TypeScript approach by default (if you set `object` type `preferredTypes` in
TypeScript mode, the defaults will not apply).

Basically, for primitives, we want to define the type as a primitive, because
that's what we use in 99.9% of cases. For everything else, we use the type
Expand Down
31 changes: 25 additions & 6 deletions README.md
Expand Up @@ -4890,11 +4890,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.<keyType, valueType>` or `Object<keyType, valueType`, perhaps to
adhere to what [JSDoc documents](https://jsdoc.app/tags-type.html).
adhere to that which [JSDoc documents](https://jsdoc.app/tags-type.html).

So, for optimal compatibility with TypeScript (especially since TypeScript
tools can be used on plain JavaScript with JSDoc), we are now allowing this
TypeScript approach by default.
tools can be used on plain JavaScript with JSDoc), we are now requiring this
TypeScript approach by default (if you set `object` type `preferredTypes` in
TypeScript mode, the defaults will not apply).

Basically, for primitives, we want to define the type as a primitive, because
that's what we use in 99.9% of cases. For everything else, we use the type
Expand Down Expand Up @@ -5498,7 +5499,7 @@ function quux (foo) {
function a () {}

/**
* @typedef {Object} foo
* @typedef {Object<string>} foo
*/
function b () {}
// Settings: {"jsdoc":{"mode":"typescript","preferredTypes":{"object":"Object"}}}
Expand Down Expand Up @@ -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.<string>} 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.<string>} foo
*/
function quux (foo) {

}
// Settings: {"jsdoc":{"mode":"typescript"}}
// Message: Invalid JSDoc @param "foo" type "object"; prefer: "Object<>".
````

The following patterns are not considered problems:
Expand Down Expand Up @@ -5817,7 +5836,7 @@ function b () {}
function a () {}

/**
* @typedef {Object} foo
* @typedef {Object<string>} foo
*/
function b () {}
// Settings: {"jsdoc":{"mode":"typescript"}}
Expand Down Expand Up @@ -5846,7 +5865,7 @@ function quux (foo) {
}

/**
* @param {Object.<string>} foo
* @param {Object<string>} foo
*/
function quux (foo) {

Expand Down
104 changes: 60 additions & 44 deletions src/rules/checkTypes.js
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
72 changes: 68 additions & 4 deletions test/rules/assertions/checkTypes.js
Expand Up @@ -2048,7 +2048,7 @@ export default {
function a () {}
/**
* @typedef {Object} foo
* @typedef {Object<string>} foo
*/
function b () {}
`,
Expand All @@ -2065,7 +2065,7 @@ export default {
function a () {}
/**
* @typedef {Object} foo
* @typedef {Object<string>} foo
*/
function b () {}
`,
Expand Down Expand Up @@ -2292,6 +2292,70 @@ export default {
},
},
},
{
code: `
/**
* @param {object.<string>} foo
*/
function quux (foo) {
}
`,
errors: [
{
line: 3,
message: 'Invalid JSDoc @param "foo" type "object"; prefer: "Object<>".',
},
],
output: `
/**
* @param {Object<string>} foo
*/
function quux (foo) {
}
`,
settings: {
jsdoc: {
mode: 'typescript',
preferredTypes: {
Object: 'object',
'object.<>': 'Object<>',
'Object.<>': 'Object<>',
'object<>': 'Object<>',
},
},
},
},
{
code: `
/**
* @param {object.<string>} foo
*/
function quux (foo) {
}
`,
errors: [
{
line: 3,
message: 'Invalid JSDoc @param "foo" type "object"; prefer: "Object<>".',
},
],
output: `
/**
* @param {Object<string>} foo
*/
function quux (foo) {
}
`,
settings: {
jsdoc: {
mode: 'typescript',
},
},
},
],
valid: [
{
Expand Down Expand Up @@ -2815,7 +2879,7 @@ export default {
function a () {}
/**
* @typedef {Object} foo
* @typedef {Object<string>} foo
*/
function b () {}
`,
Expand Down Expand Up @@ -2891,7 +2955,7 @@ export default {
{
code: `
/**
* @param {Object.<string>} foo
* @param {Object<string>} foo
*/
function quux (foo) {
Expand Down

0 comments on commit 0f27282

Please sign in to comment.