diff --git a/scripts/docs/templates/common.template.html b/scripts/docs/templates/common.template.html index 98f1656914..a0d049f313 100644 --- a/scripts/docs/templates/common.template.html +++ b/scripts/docs/templates/common.template.html @@ -179,10 +179,40 @@ Improve this doc <@ endif @> - + +<@- if doc.statics.length -@> +

Static Methods

+<@- for method in doc.statics @><@ if not method.internal @> +
+

<$ functionSyntax(method) $>

+ +<$ method.description $> + +<@ if method.params @> +<$ paramTable(method.params) $> +<@ endif @> + +<@ if method.this @> +

Method's `this` +<$ method.this $> +

+<@ endif @> + +<@ if method.returns @> +
+ +Returns: <$ typeInfo(method.returns) $> +
+<@ endif @> +<@ endif @> +<@ endfor -@> +<@ endif @> + + + <@- if doc.members and doc.members.length @> -

Methods

+

Instance Methods

<@- for method in doc.members @>
diff --git a/scripts/docs/typescript-package/index.js b/scripts/docs/typescript-package/index.js old mode 100644 new mode 100755 index d31dc77440..876c080855 --- a/scripts/docs/typescript-package/index.js +++ b/scripts/docs/typescript-package/index.js @@ -1,5 +1,3 @@ -//require('../../tools/transpiler/index.js').init(); - var basePackage = require('dgeni-packages/base'); var Package = require('dgeni').Package; var path = require('canonical-path'); @@ -16,6 +14,8 @@ module.exports = new Package('typescript-parsing', [basePackage]) .factory(require('./services/tsParser/getContent')) .factory(require('./services/tsParser/getDirectiveInfo')) +.factory(require('./services/convertPrivateClassesToInterfaces')) + .factory('EXPORT_DOC_TYPES', function() { return [ 'class', @@ -23,6 +23,7 @@ module.exports = new Package('typescript-parsing', [basePackage]) 'function', 'var', 'const', + 'let', 'enum', 'type-alias' ]; @@ -45,16 +46,18 @@ module.exports = new Package('typescript-parsing', [basePackage]) computeIdsProcessor.idTemplates.push({ docTypes: ['member'], idTemplate: '${classDoc.id}.${name}', - getAliases: function(doc) { return [doc.id]; } + getAliases: function(doc) { + return doc.classDoc.aliases.map(function(alias) { return alias + '.' + doc.name; }); + } }); computePathsProcessor.pathTemplates.push({ docTypes: ['member'], - pathTemplate: '${classDoc.path}/${name}', + pathTemplate: '${classDoc.path}#${name}', getOutputPath: function() {} // These docs are not written to their own file, instead they are part of their class doc }); - var MODULES_DOCS_PATH = 'docs'; + var MODULES_DOCS_PATH = 'partials/modules'; computePathsProcessor.pathTemplates.push({ docTypes: ['module'], diff --git a/scripts/docs/typescript-package/mocks/mockPackage.js b/scripts/docs/typescript-package/mocks/mockPackage.js old mode 100644 new mode 100755 diff --git a/scripts/docs/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts b/scripts/docs/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts old mode 100644 new mode 100755 diff --git a/scripts/docs/typescript-package/mocks/readTypeScriptModules/interfaces.ts b/scripts/docs/typescript-package/mocks/readTypeScriptModules/interfaces.ts old mode 100644 new mode 100755 diff --git a/scripts/docs/typescript-package/mocks/readTypeScriptModules/orderingOfMembers.ts b/scripts/docs/typescript-package/mocks/readTypeScriptModules/orderingOfMembers.ts old mode 100644 new mode 100755 diff --git a/scripts/docs/typescript-package/mocks/readTypeScriptModules/privateModule.ts b/scripts/docs/typescript-package/mocks/readTypeScriptModules/privateModule.ts new file mode 100755 index 0000000000..d4c6ef610a --- /dev/null +++ b/scripts/docs/typescript-package/mocks/readTypeScriptModules/privateModule.ts @@ -0,0 +1 @@ +export var x = 10; \ No newline at end of file diff --git a/scripts/docs/typescript-package/mocks/readTypeScriptModules/publicModule.ts b/scripts/docs/typescript-package/mocks/readTypeScriptModules/publicModule.ts new file mode 100755 index 0000000000..0fbae4ad2f --- /dev/null +++ b/scripts/docs/typescript-package/mocks/readTypeScriptModules/publicModule.ts @@ -0,0 +1,3 @@ +export { x as y} from './privateModule'; + +export abstract class AbstractClass {} \ No newline at end of file diff --git a/scripts/docs/typescript-package/mocks/tsParser/importedSrc.ts b/scripts/docs/typescript-package/mocks/tsParser/importedSrc.ts old mode 100644 new mode 100755 diff --git a/scripts/docs/typescript-package/mocks/tsParser/testSrc.ts b/scripts/docs/typescript-package/mocks/tsParser/testSrc.ts old mode 100644 new mode 100755 diff --git a/scripts/docs/typescript-package/processors/readTypeScriptModules.js b/scripts/docs/typescript-package/processors/readTypeScriptModules.js old mode 100644 new mode 100755 index b8243b9825..aa80c7a15c --- a/scripts/docs/typescript-package/processors/readTypeScriptModules.js +++ b/scripts/docs/typescript-package/processors/readTypeScriptModules.js @@ -4,8 +4,9 @@ var _ = require('lodash'); var ts = require('typescript'); module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, + getDirectiveInfo, getExportDocType, getContent, - getDirectiveInfo, log) { + createDocMessage, log) { return { $runAfter: ['files-read'], @@ -61,13 +62,26 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, // If the symbol is an Alias then for most things we want the original resolved symbol var resolvedExport = exportSymbol.resolvedSymbol || exportSymbol; + + // If the resolved symbol contains no declarations then it is invalid + // (probably an abstract class) + // For the moment we are just going to ignore such exports + // TODO: find a way of generating docs for them + if (!resolvedExport.declarations) return; + var exportDoc = createExportDoc(exportSymbol.name, resolvedExport, moduleDoc, basePath, parseInfo.typeChecker); log.debug('>>>> EXPORT: ' + exportDoc.name + ' (' + exportDoc.docType + ') from ' + moduleDoc.id); + // Add this export doc to its module doc + moduleDoc.exports.push(exportDoc); + docs.push(exportDoc); + + exportDoc.members = []; + exportDoc.statics = []; + // Generate docs for each of the export's members if (resolvedExport.flags & ts.SymbolFlags.HasMembers) { - exportDoc.members = []; for(var memberName in resolvedExport.members) { // FIXME(alexeagle): why do generic type params appear in members? if (memberName === 'T') { @@ -92,26 +106,40 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, exportDoc.newMember = memberDoc; } } + } - if (sortClassMembers) { - exportDoc.members.sort(function(a, b) { - if (a.name > b.name) return 1; - if (a.name < b.name) return -1; - return 0; - }); + if (exportDoc.docType === 'enum') { + for(var memberName in resolvedExport.exports) { + log.silly('>>>>>> member: ' + memberName + ' from ' + exportDoc.id + ' in ' + moduleDoc.id); + var memberSymbol = resolvedExport.exports[memberName]; + var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker); + docs.push(memberDoc); + exportDoc.members.push(memberDoc); + } + } else if (resolvedExport.flags & ts.SymbolFlags.HasExports) { + for (var exported in resolvedExport.exports) { + if (exported === 'prototype') continue; + if (hidePrivateMembers && exported.charAt(0) === '_') continue; + var memberSymbol = resolvedExport.exports[exported]; + var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker); + memberDoc.isStatic = true; + docs.push(memberDoc); + exportDoc.statics.push(memberDoc); } } - if (exportDoc.docType == 'enum') { - exportDoc.members = []; - for (var etype in resolvedExport.exports) { - exportDoc.members.push(etype); - } + if (sortClassMembers) { + exportDoc.members.sort(function(a, b) { + if (a.name > b.name) return 1; + if (a.name < b.name) return -1; + return 0; + }); + exportDoc.statics.sort(function(a, b) { + if (a.name > b.name) return 1; + if (a.name < b.name) return -1; + return 0; + }); } - - // Add this export doc to its module doc - moduleDoc.exports.push(exportDoc); - docs.push(exportDoc); }); }); } @@ -120,10 +148,12 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, function createModuleDoc(moduleSymbol, basePath) { var id = moduleSymbol.name.replace(/^"|"$/g, ''); + var name = id.split('/').pop(); var moduleDoc = { docType: 'module', + name: name, id: id, - aliases: [id], + aliases: [id, name], moduleTree: moduleSymbol, content: getContent(moduleSymbol), exports: [], @@ -136,6 +166,7 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, function createExportDoc(name, exportSymbol, moduleDoc, basePath, typeChecker) { var typeParamString = ''; var heritageString = ''; + var typeDefinition = ''; exportSymbol.declarations.forEach(function(decl) { var sourceFile = ts.getSourceFileOfNode(decl); @@ -144,6 +175,10 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, typeParamString = '<' + getText(sourceFile, decl.typeParameters) + '>'; } + if (decl.symbol.flags & ts.SymbolFlags.TypeAlias) { + typeDefinition = getText(sourceFile, decl.type); + } + if (decl.heritageClauses) { decl.heritageClauses.forEach(function(heritage) { @@ -173,10 +208,12 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, var exportDoc = { docType: getExportDocType(exportSymbol), + exportSymbol: exportSymbol, name: name, id: moduleDoc.id + '/' + name, typeParams: typeParamString, heritage: heritageString, + decorators: getDecorators(exportSymbol), aliases: aliasNames, moduleDoc: moduleDoc, content: getContent(exportSymbol), @@ -185,12 +222,30 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, directiveInfo: getDirectiveInfo(exportSymbol) }; + if (exportDoc.docType === 'var' || exportDoc.docType === 'const' || exportDoc.docType === 'let') { + exportDoc.symbolTypeName = exportSymbol.valueDeclaration.type && + exportSymbol.valueDeclaration.type.typeName && + exportSymbol.valueDeclaration.type.typeName.text; + } + + if (exportDoc.docType === 'type-alias') { + exportDoc.returnType = getReturnType(typeChecker, exportSymbol); + } + if(exportSymbol.flags & ts.SymbolFlags.Function) { exportDoc.parameters = getParameters(typeChecker, exportSymbol); } if(exportSymbol.flags & ts.SymbolFlags.Value) { exportDoc.returnType = getReturnType(typeChecker, exportSymbol); } + if (exportSymbol.flags & ts.SymbolFlags.TypeAlias) { + exportDoc.typeDefinition = typeDefinition; + } + + // Compute the original module name from the relative file path + exportDoc.originalModule = exportDoc.fileInfo.relativePath + .replace(new RegExp('\.' + exportDoc.fileInfo.extension + '$'), ''); + return exportDoc; } @@ -199,6 +254,7 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, docType: 'member', classDoc: classDoc, name: memberSymbol.name, + decorators: getDecorators(memberSymbol), content: getContent(memberSymbol), fileInfo: getFileInfo(memberSymbol, basePath), location: getLocation(memberSymbol) @@ -242,6 +298,45 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, return memberDoc; } + + function getDecorators(symbol) { + + var declaration = symbol.valueDeclaration || symbol.declarations[0]; + var sourceFile = ts.getSourceFileOfNode(declaration); + + var decorators = declaration.decorators && declaration.decorators.map(function(decorator) { + decorator = decorator.expression; + return { + name: decorator.expression ? decorator.expression.text : decorator.text, + arguments: decorator.arguments && decorator.arguments.map(function(argument) { + return getText(sourceFile, argument).trim(); + }), + argumentInfo: decorator.arguments && decorator.arguments.map(function(argument) { + return parseArgument(argument); + }), + expression: decorator + }; + }); + return decorators; + } + + function parseProperties(properties) { + var result = {}; + _.forEach(properties, function(property) { + result[property.name.text] = parseArgument(property.initializer); + }); + return result; + } + + function parseArgument(argument) { + if (argument.text) return argument.text; + if (argument.properties) return parseProperties(argument.properties); + if (argument.elements) return argument.elements.map(function(element) { return element.text; }); + var sourceFile = ts.getSourceFileOfNode(argument); + var text = getText(sourceFile, argument).trim(); + return text; + } + function getParameters(typeChecker, symbol) { var declaration = symbol.valueDeclaration || symbol.declarations[0]; var sourceFile = ts.getSourceFileOfNode(declaration); @@ -252,7 +347,11 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, ' at line ' + location.start.line); } return declaration.parameters.map(function(parameter) { - var paramText = getText(sourceFile, parameter.name); + var paramText = ''; + if (parameter.dotDotDotToken) { + paramText += '...'; + } + paramText += getText(sourceFile, parameter.name); if (parameter.questionToken || parameter.initializer) { paramText += '?'; } @@ -260,6 +359,9 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, paramText += ':' + getType(sourceFile, parameter.type); } else { paramText += ': any'; + if (parameter.dotDotDotToken) { + paramText += '[]'; + } } return paramText.trim(); }); @@ -280,6 +382,14 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, var sourceFile = ts.getSourceFileOfNode(declaration); if (declaration.type) { return getType(sourceFile, declaration.type).trim(); + } else if (declaration.initializer) { + // The symbol does not have a "type" but it is being initialized + // so we can deduce the type of from the initializer (mostly). + if (declaration.initializer.expression) { + return declaration.initializer.expression.text.trim(); + } else { + return getType(sourceFile, declaration.initializer).trim(); + } } } @@ -302,8 +412,8 @@ module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, function getType(sourceFile, type) { var text = getText(sourceFile, type); while (text.indexOf(".") >= 0) { - // Keep namespaced symbols in Rx - if (text.match(/^\s*Rx\./)) break; + // Keep namespaced symbols in RxNext + if (text.match(/^\s*RxNext\./)) break; // handle the case List -> List text = text.replace(/([^.<]*)\.([^>]*)/, "$2"); } diff --git a/scripts/docs/typescript-package/processors/readTypeScriptModules.spec.js b/scripts/docs/typescript-package/processors/readTypeScriptModules.spec.js old mode 100644 new mode 100755 index 53d959c70c..1224acc58d --- a/scripts/docs/typescript-package/processors/readTypeScriptModules.spec.js +++ b/scripts/docs/typescript-package/processors/readTypeScriptModules.spec.js @@ -13,6 +13,27 @@ describe('readTypeScriptModules', function() { processor.basePath = path.resolve(__dirname, '../mocks/readTypeScriptModules'); }); + describe('exportDocs', function() { + it('should provide the original module if the export is re-exported', function() { + processor.sourceFiles = [ 'publicModule.ts' ]; + var docs = []; + processor.$process(docs); + + var exportedDoc = docs[1]; + expect(exportedDoc.originalModule).toEqual('privateModule'); + }); + + it('should include exported abstract classes', function() { + processor.sourceFiles = [ 'publicModule.ts' ]; + var docs = []; + processor.$process(docs); + + var exportedDoc = docs[2]; + expect(exportedDoc.name).toEqual('AbstractClass'); + }); + + }); + describe('ignoreExportsMatching', function() { it('should ignore exports that match items in the `ignoreExportsMatching` property', function() { diff --git a/scripts/docs/typescript-package/services/convertPrivateClassesToInterfaces.js b/scripts/docs/typescript-package/services/convertPrivateClassesToInterfaces.js new file mode 100755 index 0000000000..69afdea89e --- /dev/null +++ b/scripts/docs/typescript-package/services/convertPrivateClassesToInterfaces.js @@ -0,0 +1,31 @@ +var _ = require('lodash'); + +module.exports = function convertPrivateClassesToInterfaces() { + return function(exportDocs, addInjectableReference) { + _.forEach(exportDocs, function(exportDoc) { + + // Search for classes with a constructor marked as `@internal` + if (exportDoc.docType === 'class' && exportDoc.constructorDoc && exportDoc.constructorDoc.internal) { + + // Convert this class to an interface with no constructor + exportDoc.docType = 'interface'; + exportDoc.constructorDoc = null; + + if (exportDoc.heritage) { + // convert the heritage since interfaces use `extends` not `implements` + exportDoc.heritage = exportDoc.heritage.replace('implements', 'extends'); + } + + if (addInjectableReference) { + // Add the `declare var SomeClass extends InjectableReference` construct + exportDocs.push({ + docType: 'var', + name: exportDoc.name, + id: exportDoc.id, + returnType: 'InjectableReference' + }); + } + } + }); + }; +}; diff --git a/scripts/docs/typescript-package/services/convertPrivateClassesToInterfaces.spec.js b/scripts/docs/typescript-package/services/convertPrivateClassesToInterfaces.spec.js new file mode 100755 index 0000000000..bc4ed7411c --- /dev/null +++ b/scripts/docs/typescript-package/services/convertPrivateClassesToInterfaces.spec.js @@ -0,0 +1,76 @@ +var mockPackage = require('../mocks/mockPackage'); +var Dgeni = require('dgeni'); +var _ = require('lodash'); + +describe('readTypeScriptModules', function() { + var dgeni, injector, convertPrivateClassesToInterfaces; + + beforeEach(function() { + dgeni = new Dgeni([mockPackage()]); + injector = dgeni.configureInjector(); + convertPrivateClassesToInterfaces = injector.get('convertPrivateClassesToInterfaces'); + }); + + it('should convert private class docs to interface docs', function() { + var docs = [ + { + docType: 'class', + name: 'privateClass', + id: 'privateClass', + constructorDoc: { internal: true } + } + ]; + convertPrivateClassesToInterfaces(docs, false); + expect(docs[0].docType).toEqual('interface'); + }); + + + it('should not touch non-private class docs', function() { + var docs = [ + { + docType: 'class', + name: 'privateClass', + id: 'privateClass', + constructorDoc: { } + } + ]; + convertPrivateClassesToInterfaces(docs, false); + expect(docs[0].docType).toEqual('class'); + }); + + + it('should convert the heritage since interfaces use `extends` not `implements`', function() { + var docs = [ + { + docType: 'class', + name: 'privateClass', + id: 'privateClass', + constructorDoc: { internal: true }, + heritage: 'implements parentInterface' + } + ]; + convertPrivateClassesToInterfaces(docs, false); + expect(docs[0].heritage).toEqual('extends parentInterface'); + }); + + + it('should add new injectable reference types, if specified, to the passed in collection', function() { + var docs = [ + { + docType: 'class', + name: 'privateClass', + id: 'privateClass', + constructorDoc: { internal: true }, + heritage: 'implements parentInterface' + } + ]; + convertPrivateClassesToInterfaces(docs, true); + expect(docs[1]).toEqual({ + docType : 'var', + name : 'privateClass', + id : 'privateClass', + returnType : 'InjectableReference' + }); + }); + +}); diff --git a/scripts/docs/typescript-package/services/modules.js b/scripts/docs/typescript-package/services/modules.js old mode 100644 new mode 100755 diff --git a/scripts/docs/typescript-package/services/tsParser/createCompilerHost.js b/scripts/docs/typescript-package/services/tsParser/createCompilerHost.js old mode 100644 new mode 100755 index 0dc488b0b8..c9d3d368b4 --- a/scripts/docs/typescript-package/services/tsParser/createCompilerHost.js +++ b/scripts/docs/typescript-package/services/tsParser/createCompilerHost.js @@ -58,17 +58,22 @@ module.exports = function createCompilerHost(log) { return ts.sys.newLine; }, fileExists: function(fileName) { - var resolvedPath = path.resolve(baseDir, fileName); - try { - fs.statSync(resolvedPath); - return true; - } catch (e) { - return false; + var text, resolvedPath, resolvedPathWithExt; + + // Strip off the extension and resolve relative to the baseDir + baseFilePath = fileName.replace(/\.[^.]+$/, ''); + resolvedPath = path.resolve(baseDir, baseFilePath); + + // Iterate through each possible extension and return the first source file that is actually found + for(var i=0; i