Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extra TS syntax support #50

Closed
143 changes: 82 additions & 61 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ const addInherited = require('jsdoc/augment').addInherited; // eslint-disable-li
const config = env.conf.typescript;
if (!config) {
throw new Error(
'Configuration "typescript" for jsdoc-plugin-typescript missing.',
'Configuration "typescript" for jsdoc-plugin-typescript missing.'
);
}
if (!('moduleRoot' in config)) {
throw new Error(
'Configuration "typescript.moduleRoot" for jsdoc-plugin-typescript missing.',
'Configuration "typescript.moduleRoot" for jsdoc-plugin-typescript missing.'
);
}
const moduleRoot = config.moduleRoot;
Expand All @@ -21,7 +21,7 @@ if (!fs.existsSync(moduleRootAbsolute)) {
throw new Error(
'Directory "' +
moduleRootAbsolute +
'" does not exist. Check the "typescript.moduleRoot" config option for jsdoc-plugin-typescript',
'" does not exist. Check the "typescript.moduleRoot" config option for jsdoc-plugin-typescript'
);
}

Expand All @@ -47,7 +47,7 @@ function getModuleInfo(moduleId, extension, parser) {
const absolutePath = path.join(
process.cwd(),
moduleRoot,
moduleId + extension,
moduleId + extension
);
if (!fs.existsSync(absolutePath)) {
return null;
Expand Down Expand Up @@ -92,51 +92,72 @@ function getDelimiter(moduleId, symbol, parser) {
}

exports.defineTags = function (dictionary) {
['type', 'typedef', 'property', 'return', 'param', 'template'].forEach(
function (tagName) {
const tag = dictionary.lookUp(tagName);
const oldOnTagText = tag.onTagText;
tag.onTagText = function (tagText) {
if (oldOnTagText) {
tagText = oldOnTagText.apply(this, arguments);
}
// Replace `templateliteral` with 'templateliteral'
const startIndex = tagText.search('{');
if (startIndex === -1) {
return tagText;
}
const len = tagText.length;
let open = 0;
let i = startIndex;
while (i < len) {
switch (tagText[i]) {
case '\\':
// Skip escaped character
++i;
break;
case '{':
++open;
break;
case '}':
if (!--open) {
return (
tagText.slice(0, startIndex) +
tagText
.slice(startIndex, i + 1)
.replace(/`([^`]*)`/g, "'$1'") +
tagText.slice(i + 1)
);
}
break;
default:
break;
}
++i;
const tags = [
'type',
'typedef',
'property',
'return',
'param',
'template',
'default',
'member',
];

tags.forEach(function (tagName) {
const tag = dictionary.lookUp(tagName);
const oldOnTagText = tag.onTagText;
tag.onTagText = function (tagText) {
if (oldOnTagText) {
tagText = oldOnTagText.apply(this, arguments);
}

const startIndex = tagText.search('{');
if (startIndex === -1) {
return tagText;
}
const len = tagText.length;
let open = 0;
let i = startIndex;
while (i < len) {
switch (tagText[i]) {
case '\\':
// Skip escaped character
++i;
break;
case '{':
++open;
break;
case '}':
if (!--open) {
return (
tagText.slice(0, startIndex) +
tagText
.slice(startIndex, i + 1)
// Replace `templateliteral` with 'templateliteral'
.replace(/`([^`]*)`/g, "'$1'")
// Interface style semi-colon separators to commas
.replace(/;/g, ',')
// Remove trailing commas in object types
.replace(/,(\s*\})/g, '$1')
ahocevar marked this conversation as resolved.
Show resolved Hide resolved
// TS-style param typing isn't supported, so just strip spaces after all colons
.replace(/:\s*/g, ':')
// Bracket notation to dot notation
.replace(
/(\w+|>|\)|\])\[(?:'([^']+)'|"([^"]+)")\]/g,
'$1.$2$3'
) +
tagText.slice(i + 1)
);
}
break;
default:
break;
}
throw new Error("Missing closing '}'");
};
},
);
++i;
}
throw new Error("Missing closing '}'");
};
});
};

exports.astNodeVisitor = {
Expand Down Expand Up @@ -229,10 +250,10 @@ exports.astNodeVisitor = {
if (
leadingComments.length === 0 ||
(leadingComments[leadingComments.length - 1].value.indexOf(
'@classdesc',
'@classdesc'
) === -1 &&
noClassdescRegEx.test(
leadingComments[leadingComments.length - 1].value,
leadingComments[leadingComments.length - 1].value
))
) {
// Create a suitable comment node if we don't have one on the class yet
Expand All @@ -250,7 +271,7 @@ exports.astNodeVisitor = {
if (node.superClass) {
// Remove the `@extends` tag because JSDoc does not does not handle generic type. (`@extends {Base<Type>}`)
const extendsIndex = lines.findIndex((line) =>
line.includes('@extends'),
line.includes('@extends')
);
if (extendsIndex !== -1) {
lines.splice(extendsIndex, 1);
Expand All @@ -262,7 +283,7 @@ exports.astNodeVisitor = {
if (identifier) {
const absolutePath = path.resolve(
path.dirname(currentSourceName),
identifier.value,
identifier.value
);
// default to js extension since .js extention is assumed implicitly
const extension = getExtension(absolutePath);
Expand Down Expand Up @@ -295,7 +316,7 @@ exports.astNodeVisitor = {
// Replace typeof Foo with Class<Foo>
comment.value = comment.value.replace(
/typeof ([^,\|\}\>]*)([,\|\}\>])/g,
'Class<$1>$2',
'Class<$1>$2'
);

// Remove `@override` annotations to avoid JSDoc breaking the inheritance chain
Expand Down Expand Up @@ -323,7 +344,7 @@ exports.astNodeVisitor = {
if (replaceAttempt > 100) {
// infinite loop protection
throw new Error(
`Invalid docstring ${comment.value} in ${currentSourceName}.`,
`Invalid docstring ${comment.value} in ${currentSourceName}.`
);
}
} else {
Expand All @@ -332,7 +353,7 @@ exports.astNodeVisitor = {
lastImportPath = importExpression;
const rel = path.resolve(
path.dirname(currentSourceName),
importSource,
importSource
);
// default to js extension since .js extention is assumed implicitly
const extension = getExtension(rel);
Expand All @@ -356,7 +377,7 @@ exports.astNodeVisitor = {
if (replacement) {
comment.value = comment.value.replace(
importExpression,
replacement + remainder,
replacement + remainder
);
}
}
Expand All @@ -377,13 +398,13 @@ exports.astNodeVisitor = {
Object.keys(identifiers).forEach((key) => {
const eventRegex = new RegExp(
`@(event |fires )${key}([^A-Za-z])`,
'g',
'g'
);
replace(eventRegex);

const typeRegex = new RegExp(
`@(.*[{<|,(!?:]\\s*)${key}([^A-Za-z].*?\}|\})`,
'g',
'g'
);
replace(typeRegex);

Expand All @@ -392,7 +413,7 @@ exports.astNodeVisitor = {
const identifier = identifiers[key];
const absolutePath = path.resolve(
path.dirname(currentSourceName),
identifier.value,
identifier.value
);
// default to js extension since .js extention is assumed implicitly
const extension = getExtension(absolutePath);
Expand All @@ -408,11 +429,11 @@ exports.astNodeVisitor = {
: getDelimiter(moduleId, exportName, parser);
const replacement = `module:${moduleId.replace(
slashRegEx,
'/',
'/'
)}${exportName ? delimiter + exportName : ''}`;
comment.value = comment.value.replace(
regex,
'@$1' + replacement + '$2',
'@$1' + replacement + '$2'
);
}
}
Expand Down