react-docgen-typescript
Advanced tools
Comparing version
@@ -117,3 +117,16 @@ import * as ts from 'typescript'; | ||
private readonly shouldIncludeExpression; | ||
private propertiesOfPropsCache; | ||
private componentsInfoCache; | ||
constructor(program: ts.Program, opts: ParserOptions); | ||
getTypeSymbol(exp: ts.Symbol): ts.Symbol; | ||
isPlainObjectType(exp: ts.Symbol): boolean; | ||
/** | ||
* Attempts to gather a symbol's exports. | ||
* Some symbol's like `default` exports are aliased, so we need to get the real symbol. | ||
* @param exp symbol | ||
*/ | ||
getComponentExports(exp: ts.Symbol): { | ||
symbol: ts.Symbol; | ||
exports: ts.SymbolTable; | ||
} | undefined; | ||
private getComponentFromExpression; | ||
@@ -120,0 +133,0 @@ getComponentInfo(exp: ts.Symbol, source: ts.SourceFile, componentNameResolver?: ComponentNameResolver, customComponentTypes?: ParserOptions['customComponentTypes']): ComponentDoc | null; |
@@ -29,3 +29,4 @@ "use strict"; | ||
module: ts.ModuleKind.CommonJS, | ||
target: ts.ScriptTarget.Latest | ||
target: ts.ScriptTarget.Latest, | ||
esModuleInterop: true | ||
}; | ||
@@ -101,2 +102,4 @@ /** | ||
function Parser(program, opts) { | ||
this.propertiesOfPropsCache = new Map(); | ||
this.componentsInfoCache = new Map(); | ||
var savePropValueAsString = opts.savePropValueAsString, shouldExtractLiteralValuesFromEnum = opts.shouldExtractLiteralValuesFromEnum, shouldRemoveUndefinedFromOptional = opts.shouldRemoveUndefinedFromOptional, shouldExtractValuesFromUnion = opts.shouldExtractValuesFromUnion, shouldSortUnions = opts.shouldSortUnions, shouldIncludePropTagMap = opts.shouldIncludePropTagMap, shouldIncludeExpression = opts.shouldIncludeExpression; | ||
@@ -113,4 +116,56 @@ this.checker = program.getTypeChecker(); | ||
} | ||
Parser.prototype.getTypeSymbol = function (exp) { | ||
var declaration = exp.valueDeclaration || exp.declarations[0]; | ||
var type = this.checker.getTypeOfSymbolAtLocation(exp, declaration); | ||
var typeSymbol = type.symbol || type.aliasSymbol; | ||
return typeSymbol; | ||
}; | ||
Parser.prototype.isPlainObjectType = function (exp) { | ||
var targetSymbol = exp; | ||
if (exp.flags & ts.SymbolFlags.Alias) { | ||
targetSymbol = this.checker.getAliasedSymbol(exp); | ||
} | ||
var declaration = targetSymbol.valueDeclaration; | ||
if (!declaration || ts.isClassDeclaration(declaration)) { | ||
return false; | ||
} | ||
var type = this.checker.getTypeOfSymbolAtLocation(targetSymbol, declaration); | ||
// Confirm it's an object type | ||
if (!(type.flags & ts.TypeFlags.Object)) { | ||
return false; | ||
} | ||
var objectType = type; | ||
var isPlain = !!(objectType.objectFlags & | ||
(ts.ObjectFlags.Anonymous | ts.ObjectFlags.ObjectLiteral)); | ||
return isPlain; | ||
}; | ||
/** | ||
* Attempts to gather a symbol's exports. | ||
* Some symbol's like `default` exports are aliased, so we need to get the real symbol. | ||
* @param exp symbol | ||
*/ | ||
Parser.prototype.getComponentExports = function (exp) { | ||
var targetSymbol = exp; | ||
if (targetSymbol.exports) { | ||
return { symbol: targetSymbol, exports: targetSymbol.exports }; | ||
} | ||
if (exp.flags & ts.SymbolFlags.Alias) { | ||
targetSymbol = this.checker.getAliasedSymbol(exp); | ||
} | ||
if (targetSymbol.exports) { | ||
return { symbol: targetSymbol, exports: targetSymbol.exports }; | ||
} | ||
}; | ||
Parser.prototype.getComponentFromExpression = function (exp) { | ||
var declaration = exp.valueDeclaration || exp.declarations[0]; | ||
// Lookup component if it's a property assignment | ||
if (declaration && ts.isPropertyAssignment(declaration)) { | ||
if (ts.isIdentifier(declaration.initializer)) { | ||
var newSymbol = this.checker.getSymbolAtLocation(declaration.initializer); | ||
if (newSymbol) { | ||
exp = newSymbol; | ||
declaration = exp.valueDeclaration || exp.declarations[0]; | ||
} | ||
} | ||
} | ||
var type = this.checker.getTypeOfSymbolAtLocation(exp, declaration); | ||
@@ -147,2 +202,6 @@ var typeSymbol = type.symbol || type.aliasSymbol; | ||
var filePath = source.fileName; | ||
var cacheKey = filePath + "_" + originalName; | ||
if (this.componentsInfoCache.has(cacheKey)) { | ||
return this.componentsInfoCache.get(cacheKey); | ||
} | ||
if (!rootExp.valueDeclaration) { | ||
@@ -153,2 +212,3 @@ if (!typeSymbol && (rootExp.flags & ts.SymbolFlags.Alias) !== 0) { | ||
else if (!typeSymbol) { | ||
this.componentsInfoCache.set(cacheKey, null); | ||
return null; | ||
@@ -188,2 +248,3 @@ } | ||
typeSymbol.getEscapedName() === 'Validator')) { | ||
this.componentsInfoCache.set(cacheKey, null); | ||
return null; | ||
@@ -203,2 +264,3 @@ } | ||
if (!commentSource.valueDeclaration) { | ||
this.componentsInfoCache.set(cacheKey, null); | ||
return null; | ||
@@ -239,2 +301,3 @@ } | ||
} | ||
this.componentsInfoCache.set(cacheKey, result); | ||
return result; | ||
@@ -450,36 +513,46 @@ }; | ||
var propName = prop.getName(); | ||
// Find type of prop by looking in context of the props object itself. | ||
var propType = _this.checker.getTypeOfSymbolAtLocation(prop, propsObj.valueDeclaration); | ||
var jsDocComment = _this.findDocComment(prop); | ||
var hasCodeBasedDefault = defaultProps[propName] !== undefined; | ||
var defaultValue = null; | ||
if (hasCodeBasedDefault) { | ||
defaultValue = { value: defaultProps[propName] }; | ||
var parent = getParentType(prop); | ||
var cacheKey = (parent === null || parent === void 0 ? void 0 : parent.fileName) + "_" + propName; | ||
if (_this.propertiesOfPropsCache.has(cacheKey)) { | ||
result[propName] = _this.propertiesOfPropsCache.get(cacheKey); | ||
} | ||
else if (jsDocComment.tags.default) { | ||
defaultValue = { value: jsDocComment.tags.default }; | ||
else { | ||
// Find type of prop by looking in context of the props object itself. | ||
var propType = _this.checker.getTypeOfSymbolAtLocation(prop, propsObj.valueDeclaration); | ||
var jsDocComment = _this.findDocComment(prop); | ||
var hasCodeBasedDefault = defaultProps[propName] !== undefined; | ||
var defaultValue = null; | ||
if (hasCodeBasedDefault) { | ||
defaultValue = { value: defaultProps[propName] }; | ||
} | ||
else if (jsDocComment.tags.default) { | ||
defaultValue = { value: jsDocComment.tags.default }; | ||
} | ||
var parents = getDeclarations(prop); | ||
var declarations = prop.declarations || []; | ||
var baseProp = baseProps.find(function (p) { return p.getName() === propName; }); | ||
var required = !isOptional(prop) && | ||
!hasCodeBasedDefault && | ||
// If in a intersection or union check original declaration for "?" | ||
// @ts-ignore | ||
declarations.every(function (d) { return !d.questionToken; }) && | ||
(!baseProp || !isOptional(baseProp)); | ||
var type = jsDocComment.tags.type | ||
? { | ||
name: jsDocComment.tags.type | ||
} | ||
: _this.getDocgenType(propType, required); | ||
var propTags = _this.shouldIncludePropTagMap | ||
? { tags: jsDocComment.tags } | ||
: {}; | ||
var description = _this.shouldIncludePropTagMap | ||
? jsDocComment.description.replace(/\r\n/g, '\n') | ||
: jsDocComment.fullComment.replace(/\r\n/g, '\n'); | ||
var propItem = __assign({ defaultValue: defaultValue, description: description, name: propName, parent: parent, declarations: parents, required: required, | ||
type: type }, propTags); | ||
if (parent === null || parent === void 0 ? void 0 : parent.fileName.includes('node_modules')) { | ||
_this.propertiesOfPropsCache.set(parent.fileName + "_" + propName, propItem); | ||
} | ||
result[propName] = propItem; | ||
} | ||
var parent = getParentType(prop); | ||
var parents = getDeclarations(prop); | ||
var declarations = prop.declarations || []; | ||
var baseProp = baseProps.find(function (p) { return p.getName() === propName; }); | ||
var required = !isOptional(prop) && | ||
!hasCodeBasedDefault && | ||
// If in a intersection or union check original declaration for "?" | ||
// @ts-ignore | ||
declarations.every(function (d) { return !d.questionToken; }) && | ||
(!baseProp || !isOptional(baseProp)); | ||
var type = jsDocComment.tags.type | ||
? { | ||
name: jsDocComment.tags.type | ||
} | ||
: _this.getDocgenType(propType, required); | ||
var propTags = _this.shouldIncludePropTagMap | ||
? { tags: jsDocComment.tags } | ||
: {}; | ||
var description = _this.shouldIncludePropTagMap | ||
? jsDocComment.description.replace(/\r\n/g, '\n') | ||
: jsDocComment.fullComment.replace(/\r\n/g, '\n'); | ||
result[propName] = __assign({ defaultValue: defaultValue, description: description, name: propName, parent: parent, declarations: parents, required: required, | ||
type: type }, propTags); | ||
}); | ||
@@ -737,12 +810,13 @@ return result; | ||
return properties.reduce(function (acc, property) { | ||
if (ts.isSpreadAssignment(property) || !property.name) { | ||
var propertyName = getPropertyName(ts.isBindingElement(property) | ||
? property.propertyName || property.name | ||
: property.name); | ||
if (ts.isSpreadAssignment(property) || !propertyName) { | ||
return acc; | ||
} | ||
var literalValue = _this.getLiteralValueFromPropertyAssignment(property); | ||
var propertyName = getPropertyName(property.name); | ||
if ((typeof literalValue === 'string' || | ||
if (typeof literalValue === 'string' || | ||
typeof literalValue === 'number' || | ||
typeof literalValue === 'boolean' || | ||
literalValue === null) && | ||
propertyName !== null) { | ||
literalValue === null) { | ||
acc[propertyName] = literalValue; | ||
@@ -808,11 +882,33 @@ } | ||
function getTextValueOfFunctionProperty(exp, source, propertyName) { | ||
var textValue = source.statements | ||
var identifierStatements = source.statements | ||
.filter(function (statement) { return ts.isExpressionStatement(statement); }) | ||
.filter(function (statement) { | ||
var _a, _b, _c, _d; | ||
var expr = statement | ||
.expression; | ||
var locals = Array.from(source.locals); | ||
var hasOneLocalExport = locals.filter(function (local) { return !!local[1].exports; }).length === 1; | ||
if (hasOneLocalExport) { | ||
return (expr.left && | ||
expr.left.name && | ||
expr.left.name.escapedText === | ||
propertyName); | ||
} | ||
/** | ||
* Ensure the .displayName is for the currently processing function. | ||
* | ||
* This avoids the following situations: | ||
* | ||
* - A file has multiple functions, one has `.displayName`, and all | ||
* functions ends up with that same `.displayName` value. | ||
* | ||
* - A file has multiple functions, each with a different | ||
* `.displayName`, but the first is applied to all of them. | ||
*/ | ||
var flowNodeNameEscapedText = (_d = (_c = (_b = (_a = statement) === null || _a === void 0 ? void 0 : _a.flowNode) === null || _b === void 0 ? void 0 : _b.node) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.escapedText; | ||
return (expr.left && | ||
expr.left.name && | ||
expr.left.name.escapedText === | ||
propertyName); | ||
propertyName && | ||
flowNodeNameEscapedText === exp.escapedName); | ||
}) | ||
@@ -824,6 +920,17 @@ .filter(function (statement) { | ||
.map(function (statement) { | ||
return statement | ||
.expression.right.text; | ||
})[0]; | ||
return textValue || ''; | ||
var expressionStatement = statement | ||
.expression; | ||
var name = expressionStatement.left | ||
.expression.escapedText; | ||
var value = expressionStatement.right.text; | ||
return [name, value]; | ||
}); | ||
if (identifierStatements.length > 0) { | ||
var locatedStatement = identifierStatements.find(function (statement) { return statement[0] === exp.escapedName; }); | ||
if (locatedStatement) { | ||
return locatedStatement[1]; | ||
} | ||
return identifierStatements[0][1] || ''; | ||
} | ||
return ''; | ||
} | ||
@@ -938,6 +1045,22 @@ function computeComponentName(exp, source, customComponentTypes) { | ||
} | ||
var components = checker.getExportsOfModule(moduleSymbol); | ||
var exports = checker.getExportsOfModule(moduleSymbol); | ||
var componentDocs = []; | ||
var exportsAndMembers = []; | ||
// Examine each export to determine if it's on object which may contain components | ||
exports.forEach(function (exp) { | ||
// Push symbol for extraction to maintain existing behavior | ||
exportsAndMembers.push(exp); | ||
// Determine if the export symbol is an object | ||
if (!parser.isPlainObjectType(exp)) { | ||
return; | ||
} | ||
var typeSymbol = parser.getTypeSymbol(exp); | ||
if (typeSymbol === null || typeSymbol === void 0 ? void 0 : typeSymbol.members) { | ||
typeSymbol.members.forEach(function (member) { | ||
exportsAndMembers.push(member); | ||
}); | ||
} | ||
}); | ||
// First document all components | ||
components.forEach(function (exp) { | ||
exportsAndMembers.forEach(function (exp) { | ||
var doc = parser.getComponentInfo(exp, sourceFile, parserOpts.componentNameResolver, parserOpts.customComponentTypes); | ||
@@ -947,7 +1070,8 @@ if (doc) { | ||
} | ||
if (!exp.exports) { | ||
var componentExports = parser.getComponentExports(exp); | ||
if (!componentExports) { | ||
return; | ||
} | ||
// Then document any static sub-components | ||
exp.exports.forEach(function (symbol) { | ||
componentExports.exports.forEach(function (symbol) { | ||
if (symbol.flags & ts.SymbolFlags.Prototype) { | ||
@@ -965,3 +1089,5 @@ return; | ||
if (doc) { | ||
var prefix = exp.escapedName === 'default' ? '' : exp.escapedName + "."; | ||
var prefix = componentExports.symbol.escapedName === 'default' | ||
? '' | ||
: componentExports.symbol.escapedName + "."; | ||
componentDocs.push(__assign(__assign({}, doc), { displayName: "" + prefix + symbol.escapedName })); | ||
@@ -968,0 +1094,0 @@ } |
@@ -6,5 +6,8 @@ "use strict"; | ||
var slashRegex = /[\\/]/g; | ||
var fileNameCache = new Map(); | ||
function trimFileName(fileName, cwd, platform) { | ||
var _a; | ||
if (cwd === void 0) { cwd = process.cwd(); } | ||
if (fileNameCache.has(fileName)) | ||
return fileNameCache.get(fileName); | ||
// This allows tests to run regardless of current platform | ||
@@ -25,10 +28,13 @@ var pathLib = platform ? path[platform] : path; | ||
if (normalizedFileName.startsWith(parent)) { | ||
return (pathLib | ||
var finalPathName = pathLib | ||
// Preserve the parent directory name to match existing behavior | ||
.relative(pathLib.dirname(parent), normalizedFileName) | ||
// Restore original type of slashes | ||
.replace(slashRegex, originalSep)); | ||
.replace(slashRegex, originalSep); | ||
fileNameCache.set(fileName, finalPathName); | ||
return finalPathName; | ||
} | ||
parent = pathLib.dirname(parent); | ||
} while (parent !== root); | ||
fileNameCache.set(fileName, fileName); | ||
// No common ancestor, so return the path as-is | ||
@@ -35,0 +41,0 @@ return fileName; |
{ | ||
"name": "react-docgen-typescript", | ||
"version": "2.3.0-beta.0", | ||
"version": "2.3.0-beta.1", | ||
"description": "", | ||
@@ -40,5 +40,5 @@ "homepage": "https://github.com/styleguidist/react-docgen-typescript/", | ||
"install": "^0.13.0", | ||
"lint-staged": "^7.3.0", | ||
"lint-staged": "^16.1.0", | ||
"lodash": "^4.17.15", | ||
"mocha": "^9.1.2", | ||
"mocha": "^11.5.0", | ||
"prettier": "^1.19.1", | ||
@@ -45,0 +45,0 @@ "prop-types": "^15.6.2", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
263196
7.37%3393
7.17%