Skip to content
This repository was archived by the owner on Mar 31, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ coverage

.tmp

debug-dump.txt
debug-dump.txt
.idea/
2 changes: 2 additions & 0 deletions typescript/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = new Package('typescript', [require('../jsdoc')])
.factory(require('./services/tsParser/getContent'))

.factory(require('./services/convertPrivateClassesToInterfaces'))
.factory(require('./services/typescript-symbol-map'))

.factory('EXPORT_DOC_TYPES', function() {
return [
Expand All @@ -35,6 +36,7 @@ module.exports = new Package('typescript', [require('../jsdoc')])

// Register the processors
.processor(require('./processors/readTypeScriptModules'))
.processor(require('./processors/linkInheritedDocs'))

// Configure ids and paths
.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) {
Expand Down
23 changes: 23 additions & 0 deletions typescript/mocks/readTypeScriptModules/inheritedMembers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export class LastParent implements TestInterface {
lastParentProp: number = 0;
}

export class Child extends FirstParent implements TestInterface {
childProp: boolean = false;
}

/**
* To ensure that Dgeni properly recognizes classes from exports that will be parsed later,
* the `FirstParent` class will be ordered after the `Child` class.
**/
export class FirstParent extends LastParent implements TestInterface {
firstParentProp: string = 'Works';
_privateProp: string = 'Private';
private privateProp: string = 'Private';
}

/**
* This is just a test interface that has been added to ensure that Dgeni only recognizes
* extends heritages.
**/
export interface TestInterface {}
18 changes: 18 additions & 0 deletions typescript/processors/linkInheritedDocs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Processor that links the inherited symbols to the associating dgeni doc.
**/
module.exports = function linkInheritedDocs(typescriptSymbolMap) {
return {
$runAfter: ['readTypeScriptModules'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is probably worth putting in a $runBefore property too, since we can't guarantee that it will be run before other processors otherwise...

$runBefore: ['parsing-tags'],
$process: docs => {
// Iterate through all docs and link the doc symbols if present.
docs.filter(doc => doc.inheritedSymbols).forEach(doc => linkDocSymbols(doc))
}
};

function linkDocSymbols(doc) {
doc.inheritedDocs = [];
doc.inheritedSymbols.forEach(symbol => doc.inheritedDocs.push(typescriptSymbolMap.get(symbol)));
}
};
65 changes: 65 additions & 0 deletions typescript/processors/linkInheritedDocs.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const mockPackage = require('../mocks/mockPackage');
const Dgeni = require('dgeni');
const path = require('canonical-path');

describe('linkInheritedDocs', function() {

let dgeni, injector, tsProcessor, linkProcessor = null;

beforeEach(function () {
dgeni = new Dgeni([mockPackage()]);
injector = dgeni.configureInjector();

tsProcessor = injector.get('readTypeScriptModules');
linkProcessor = injector.get('linkInheritedDocs');

// Since the `readTypeScriptModules` mock folder includes the a good amount files, the
// spec for linking the inherited docs will just use those.
tsProcessor.basePath = path.resolve(__dirname, '../mocks/readTypeScriptModules');
});

it('should properly link the inherited docs', () => {
let docsArray = [];

tsProcessor.sourceFiles = ['inheritedMembers.ts'];

tsProcessor.$process(docsArray);
linkProcessor.$process(docsArray);

let childDoc = docsArray[3];
let firstParentDoc = docsArray[5];
let lastParentDoc = docsArray[1];

expect(childDoc.inheritedDocs).toEqual([firstParentDoc]);
expect(firstParentDoc.inheritedDocs).toEqual([lastParentDoc]);
expect(lastParentDoc.inheritedDocs).toEqual([]);
});

it('should properly resolve members in inherited docs', () => {
let docsArray = [];

tsProcessor.sourceFiles = ['inheritedMembers.ts'];

tsProcessor.$process(docsArray);
linkProcessor.$process(docsArray);

let childDoc = docsArray[3];
let members = getInheritedMembers(childDoc);

expect(members.length).toBe(3);
expect(members[0].name).toBe('childProp');
expect(members[1].name).toBe('firstParentProp');
expect(members[2].name).toBe('lastParentProp');
});

/** Returns a list of all inherited members of a doc. */
function getInheritedMembers(doc) {
let members = doc.members || [];

doc.inheritedDocs.forEach(inheritedDoc => {
members = members.concat(getInheritedMembers(inheritedDoc));
});

return members;
}
});
58 changes: 53 additions & 5 deletions typescript/processors/readTypeScriptModules.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ var path = require('canonical-path');
var _ = require('lodash');
var ts = require('typescript');

module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, ignoreTypeScriptNamespaces,
getExportDocType, getExportAccessibility, getContent, createDocMessage, log) {
module.exports = function readTypeScriptModules(
tsParser, modules, getFileInfo, ignoreTypeScriptNamespaces, getExportDocType,
getExportAccessibility, getContent, createDocMessage, log, typescriptSymbolMap) {

return {
$runAfter: ['files-read'],
Expand Down Expand Up @@ -41,6 +42,7 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
var filesPaths = expandSourceFiles(this.sourceFiles, basePath);
var parseInfo = tsParser.parse(filesPaths, this.basePath);
var moduleSymbols = parseInfo.moduleSymbols;
var typeChecker = parseInfo.typeChecker;

// Iterate through each of the modules that were parsed and generate a module doc
// as well as docs for each module's exports.
Expand All @@ -67,7 +69,7 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
// TODO: find a way of generating docs for them
if (!resolvedExport.declarations) return;

var exportDoc = createExportDoc(exportSymbol.name, resolvedExport, moduleDoc, basePath, parseInfo.typeChecker);
var exportDoc = createExportDoc(exportSymbol.name, resolvedExport, moduleDoc, basePath, typeChecker);
log.debug('>>>> EXPORT: ' + exportDoc.name + ' (' + exportDoc.docType + ') from ' + moduleDoc.id);

// Add this export doc to its module doc
Expand All @@ -77,6 +79,12 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
exportDoc.members = [];
exportDoc.statics = [];

// Store the dgeni doc of the resolved symbol in the typescriptSymbolMap.
typescriptSymbolMap.set(resolvedExport, exportDoc);

// Resolve all inherited symbols and store them inside of the exportDoc object.
exportDoc.inheritedSymbols = resolveInheritedSymbols(resolvedExport, typeChecker);

// Generate docs for each of the export's members
if (resolvedExport.flags & ts.SymbolFlags.HasMembers) {

Expand All @@ -87,7 +95,7 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
}
log.silly('>>>>>> member: ' + memberName + ' from ' + exportDoc.id + ' in ' + moduleDoc.id);
var memberSymbol = resolvedExport.members[memberName];
var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker);
var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, typeChecker);

// We special case the constructor and sort the other members alphabetically
if (memberSymbol.flags & ts.SymbolFlags.Constructor) {
Expand Down Expand Up @@ -143,6 +151,42 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
}
};

/**
* Resolves all inherited class symbols of the specified symbol.
* @param {ts.Symbol} symbol ClassDeclaration symbol with members
* @param {ts.TypeChecker} typeChecker TypeScript TypeChecker for symbols
* @returns {Symbol[]} List of inherited class symbols.
**/
function resolveInheritedSymbols(symbol, typeChecker) {
var declaration = symbol.valueDeclaration || symbol.declarations[0];
var symbols = [];

if (declaration && declaration.heritageClauses) {
symbols = declaration.heritageClauses
// Filter for extends heritage clauses.
.filter(isExtendsHeritage)
// Resolve an array of the inherited symbols from the heritage clause.
.map(heritage => convertHeritageClauseToSymbols(heritage, typeChecker))
// Flatten the arrays of inherited symbols.
.reduce((symbols, cur) => symbols.concat(cur), []);
}

return symbols;
}

/**
* Converts a heritage clause into a list of symbols
* @param {ts.HeritageClause} heritage
* @param {ts.TypeChecker} typeChecker
* @returns {ts.SymbolTable} List of heritage symbols.
**/
function convertHeritageClauseToSymbols(heritage, typeChecker) {
var heritages = heritage.types.map(expression =>
typeChecker.getTypeAtLocation(expression).getSymbol()
);

return heritages.reduce((inheritances, current) => inheritances.concat(current), []);
}

function createModuleDoc(moduleSymbol, basePath) {
var id = moduleSymbol.name.replace(/^"|"$/g, '');
Expand Down Expand Up @@ -189,7 +233,7 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
if (declaration.heritageClauses) {
declaration.heritageClauses.forEach(function(heritage) {

if (heritage.token == ts.SyntaxKind.ExtendsKeyword) {
if (isExtendsHeritage(heritage)) {
heritageString += " extends";
heritage.types.forEach(function(typ, idx) {
heritageString += (idx > 0 ? ',' : '') + typ.getFullText();
Expand Down Expand Up @@ -450,6 +494,10 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo,
return location;
}

/** Checks if the specified heritage clause is an extends keyword. */
function isExtendsHeritage(heritageClause) {
return heritageClause.token === ts.SyntaxKind.ExtendsKeyword
}
};

function convertToRegexCollection(items) {
Expand Down
24 changes: 23 additions & 1 deletion typescript/processors/readTypeScriptModules.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ describe('readTypeScriptModules', function() {

});

describe('inherited symbols', function() {

it('should add the list of inherited symbols to a class doc', function() {
processor.sourceFiles = [ 'inheritedMembers.ts' ];
var docs = [];

processor.$process(docs);

var childDoc = docs[3];
var firstParentDoc = docs[5];
var lastParentDoc = docs[1];

expect(childDoc.inheritedSymbols.length).toBe(1);
expect(childDoc.inheritedSymbols[0]).toBe(firstParentDoc.exportSymbol);

expect(firstParentDoc.inheritedSymbols.length).toBe(1);
expect(firstParentDoc.inheritedSymbols[0]).toBe(lastParentDoc.exportSymbol);

expect(lastParentDoc.inheritedSymbols.length).toBe(0);
});

});

describe('ignoreExportsMatching', function() {
it('should ignore exports that match items in the `ignoreExportsMatching` property', function() {
Expand Down Expand Up @@ -235,4 +257,4 @@ describe('readTypeScriptModules', function() {

function getNames(collection) {
return collection.map(function(item) { return item.name; });
}
}
6 changes: 6 additions & 0 deletions typescript/services/typescript-symbol-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Service that can be used to store typescript symbols with their associated dgeni doc.
**/
module.exports = function typescriptSymbolMap() {
return new Map();
};