diff --git a/docs/angular.io-package/templates/class.template.html b/docs/angular.io-package/templates/class.template.html index d8faf84985..d266afeaf2 100644 --- a/docs/angular.io-package/templates/class.template.html +++ b/docs/angular.io-package/templates/class.template.html @@ -3,8 +3,8 @@ {% block body %} p.location-badge. - exported from {$ doc.moduleDoc.id $} - defined in {$ doc.location.start.source.name $}.js (line {$ doc.location.start.line $}) + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $}) :markdown {$ doc.description | indent(2, true) $} @@ -17,10 +17,10 @@ p.location-badge. .l-sub-section h3 {$ doc.constructorDoc.name $} - {% if doc.constructorDoc.params %} + {% if doc.constructorDoc.parameters %} pre.prettyprint code. - {$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.params) | indent(4, true) | trim $} + {$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.parameters) | indent(4, true) | trim $} {% endif %} :markdown {$ doc.constructorDoc.description | indent(6, true) | replace('## Example', '') | replace('# Example', '') $} @@ -32,10 +32,10 @@ p.location-badge. .l-sub-section h3 {$ member.name $} - {% if member.params %} + {% if member.parameters %} pre.prettyprint code. - {$ member.name $}{$ paramList(member.params) | indent(4, true) | trim $} + {$ member.name $}{$ paramList(member.parameters) | indent(4, true) | trim $}{$ returnType(doc.returnType) $} {% endif %} :markdown diff --git a/docs/angular.io-package/templates/function.template.html b/docs/angular.io-package/templates/function.template.html index bd198ba0fe..8a23300ea1 100644 --- a/docs/angular.io-package/templates/function.template.html +++ b/docs/angular.io-package/templates/function.template.html @@ -5,10 +5,15 @@ .l-main-section h2(class="function export") {$ doc.name $} - p {$ paramList(doc.parameters) $} + {% if doc.parameters %} + pre.prettyprint + code. + {$ doc.name $}{$ paramList(doc.parameters) | indent(4, true) | trim $}{$ returnType(doc.returnType) $} + {% endif %} p.location-badge. - exported from {$ doc.moduleDoc.id $} + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $}) :markdown {$ doc.description | indent(4, true) $} diff --git a/docs/angular.io-package/templates/lib/paramList.html b/docs/angular.io-package/templates/lib/paramList.html index b84a8d7162..24ba12c080 100644 --- a/docs/angular.io-package/templates/lib/paramList.html +++ b/docs/angular.io-package/templates/lib/paramList.html @@ -4,4 +4,9 @@ {$ param | escape $}{% if not loop.last %}, {% endif %} {%- endfor %}) {%- endif %} -{%- endmacro -%} \ No newline at end of file +{%- endmacro -%} + + +{% macro returnType(returnType) -%} + {%- if returnType %} : {$ returnType | escape $}{% endif -%} +{%- endmacro -%} diff --git a/docs/angular.io-package/templates/module.template.html b/docs/angular.io-package/templates/module.template.html index 497b3ca956..96ba98ccd6 100644 --- a/docs/angular.io-package/templates/module.template.html +++ b/docs/angular.io-package/templates/module.template.html @@ -1,5 +1,8 @@ {% extends 'layout/base.template.html' -%} {% block body -%} +p.location-badge. + defined in {$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $}) + ul for page, slug in public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]]._data if slug != 'index' diff --git a/docs/dgeni-package/index.js b/docs/dgeni-package/index.js index de52461a2f..e858075946 100644 --- a/docs/dgeni-package/index.js +++ b/docs/dgeni-package/index.js @@ -15,6 +15,11 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage, linksPac // Register the services and file readers .factory(require('./services/modules')) +.factory(require('./services/tsParser')) +.factory(require('./services/tsParser/createCompilerHost')) +.factory(require('./services/tsParser/getFileInfo')) +.factory(require('./services/tsParser/getExportDocType')) +.factory(require('./services/tsParser/getContent')) .factory(require('./readers/ngdoc')) .factory('EXPORT_DOC_TYPES', function() { @@ -28,6 +33,7 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage, linksPac // Register the processors +.processor(require('./processors/readTypeScriptModules')) .processor(require('./processors/generateNavigationDoc')) .processor(require('./processors/extractTitleFromGuides')) .processor(require('./processors/createOverviewDump')) @@ -40,13 +46,23 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage, linksPac // Configure file reading -.config(function(readFilesProcessor, ngdocFileReader) { +.config(function(readFilesProcessor, ngdocFileReader, readTypeScriptModules) { readFilesProcessor.fileReaders = [ngdocFileReader]; readFilesProcessor.basePath = path.resolve(__dirname, '../..'); readFilesProcessor.sourceFiles = [ { include: 'modules/*/docs/**/*.md', basePath: 'modules' }, { include: 'docs/content/**/*.md', basePath: 'docs/content' } ]; + + readTypeScriptModules.sourceFiles = [ + '*/*.js', + '*/src/**/*.js', + '*/*.es6', + '*/src/**/*.es6', + '*/*.ts', + '*/src/**/*.ts' + ]; + readTypeScriptModules.basePath = 'modules'; }) @@ -54,6 +70,14 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage, linksPac parseTagsProcessor.tagDefinitions.push(require('./tag-defs/public')); parseTagsProcessor.tagDefinitions.push(require('./tag-defs/private')); parseTagsProcessor.tagDefinitions.push(require('./tag-defs/exportedAs')); + + // We actually don't want to parse param docs in this package as we are getting the data out using TS + parseTagsProcessor.tagDefinitions.forEach(function(tagDef) { + if (tagDef.name === 'param') { + tagDef.ignore = true; + } + }); + }) @@ -88,12 +112,6 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage, linksPac // Configure ids and paths .config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) { - computeIdsProcessor.idTemplates.push({ - docTypes: EXPORT_DOC_TYPES, - idTemplate: '${moduleDoc.id}.${name}', - getAliases: function(doc) { return [doc.id, doc.name]; } - }); - computeIdsProcessor.idTemplates.push({ docTypes: ['member'], idTemplate: '${classDoc.id}.${name}', @@ -117,7 +135,7 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage, linksPac computePathsProcessor.pathTemplates.push({ docTypes: ['module'], - pathTemplate: '${id}', + pathTemplate: '/${id}', outputPathTemplate: MODULES_DOCS_PATH + '/${id}/index.html' }); @@ -136,7 +154,7 @@ module.exports = new Package('angular', [jsdocPackage, nunjucksPackage, linksPac computePathsProcessor.pathTemplates.push({ docTypes: ['guide'], - pathTemplate: '${id}', + pathTemplate: '/${id}', outputPathTemplate: GUIDES_PATH + '/${id}.html' }); }); diff --git a/docs/dgeni-package/mocks/importedSrc.ts b/docs/dgeni-package/mocks/importedSrc.ts new file mode 100644 index 0000000000..74a115e5fe --- /dev/null +++ b/docs/dgeni-package/mocks/importedSrc.ts @@ -0,0 +1 @@ +export var x = 100; \ No newline at end of file diff --git a/docs/dgeni-package/mocks/testSrc.ts b/docs/dgeni-package/mocks/testSrc.ts new file mode 100644 index 0000000000..159985a9e6 --- /dev/null +++ b/docs/dgeni-package/mocks/testSrc.ts @@ -0,0 +1,38 @@ +/** + * @module + * @description + * This is the module description + */ + +export * from 'importedSrc'; + +/** + * This is some random other comment + */ + +/** + * This is MyClass + */ +export class MyClass { + message: String; + + /** + * Create a new MyClass + * @param {String} name The name to say hello to + */ + constructor(name) { + this.message = 'hello ' + name; + } + + /** + * Return a greeting message + */ + greet() { + return this.message; + } +} + +/** + * An exported function + */ +export var myFn = (val:number) => { return val*2; } \ No newline at end of file diff --git a/docs/dgeni-package/processors/readTypeScriptModules.js b/docs/dgeni-package/processors/readTypeScriptModules.js new file mode 100644 index 0000000000..700252fcf0 --- /dev/null +++ b/docs/dgeni-package/processors/readTypeScriptModules.js @@ -0,0 +1,201 @@ +var glob = require('glob'); +var path = require('canonical-path'); +var _ = require('lodash'); +var ts = require('typescript'); + +module.exports = function readTypeScriptModules(tsParser, readFilesProcessor, modules, getFileInfo, getExportDocType, getContent, log) { + + return { + $runAfter: ['files-read'], + $runBefore: ['parsing-tags'], + + $validate: { + sourceFiles: {presence: true}, + basePath: {presence: true}, + hidePrivateMembers: { inclusion: [true, false] }, + hideSpecialExports: { inclusion: [true, false] } + }, + + sourceFiles: [], + basePath: '.', + hidePrivateMembers: false, + hideSpecialExports: true, + + $process: function(docs) { + + var hideSpecialExports = this.hideSpecialExports; + var hidePrivateMembers = this.hidePrivateMembers; + + var basePath = path.resolve(readFilesProcessor.basePath, this.basePath); + var filesPaths = expandSourceFiles(this.sourceFiles, basePath); + var parseInfo = tsParser.parse(filesPaths, this.basePath); + var moduleSymbols = parseInfo.moduleSymbols; + + // Iterate through each of the modules that were parsed and generate a module doc + // as well as docs for each module's exports. + moduleSymbols.forEach(function(moduleSymbol) { + + var moduleDoc = createModuleDoc(moduleSymbol, basePath); + + // Add this module doc to the module lookup collection and the docs collection + modules[moduleDoc.id] = moduleDoc; + docs.push(moduleDoc); + + // Iterate through this module's exports and generate a doc for each + moduleSymbol.exportArray.forEach(function(exportSymbol) { + + // Ignore exports starting with an underscore + if (hideSpecialExports && exportSymbol.name.charAt(0) === '_') return; + + // If the symbol is an Alias then for most things we want the original resolved symbol + var resolvedExport = exportSymbol.resolvedSymbol || exportSymbol; + var exportDoc = createExportDoc(exportSymbol.name, resolvedExport, moduleDoc, basePath, parseInfo.typeChecker); + log.debug('>>>> EXPORT: ' + exportDoc.name + ' (' + exportDoc.docType + ') from ' + moduleDoc.id); + + // Generate docs for each of the export's members + if (resolvedExport.flags & ts.SymbolFlags.HasMembers) { + + exportDoc.members = []; + for(var memberName in resolvedExport.members) { + log.silly('>>>>>> member: ' + memberName + ' from ' + exportDoc.id + ' in ' + moduleDoc.id); + var memberSymbol = resolvedExport.members[memberName]; + var memberDoc = createMemberDoc(memberSymbol, exportDoc, basePath, parseInfo.typeChecker); + + // We special case the constructor and sort the other members alphabetically + if (memberSymbol.flags & ts.SymbolFlags.Constructor) { + exportDoc.constructorDoc = memberDoc; + docs.push(memberDoc); + } else if (!hidePrivateMembers || memberSymbol.name.charAt(0) !== '_') { + docs.push(memberDoc); + insertSorted(exportDoc.members, memberDoc, 'name'); + } + } + } + + // Add this export doc to its module doc + moduleDoc.exports.push(exportDoc); + docs.push(exportDoc); + }); + }); + } + }; + + + function createModuleDoc(moduleSymbol, basePath) { + var id = moduleSymbol.name.replace(/^"|"$/g, ''); + var moduleDoc = { + docType: 'module', + id: id, + aliases: [id], + moduleTree: moduleSymbol, + content: getContent(moduleSymbol), + exports: [], + fileInfo: getFileInfo(moduleSymbol, basePath), + location: getLocation(moduleSymbol) + }; + return moduleDoc; + } + + function createExportDoc(name, exportSymbol, moduleDoc, basePath, typeChecker) { + var exportDoc = { + docType: getExportDocType(exportSymbol), + name: name, + id: name, + aliases: [name], + moduleDoc: moduleDoc, + content: getContent(exportSymbol), + fileInfo: getFileInfo(exportSymbol, basePath), + location: getLocation(exportSymbol) + }; + if(exportSymbol.flags & ts.SymbolFlags.Function) { + exportDoc.parameters = getParameters(typeChecker, exportSymbol); + exportDoc.returnType = getReturnType(typeChecker, exportSymbol); + } + return exportDoc; + } + + function createMemberDoc(memberSymbol, classDoc, basePath, typeChecker) { + var memberDoc = { + docType: 'member', + classDoc: classDoc, + name: memberSymbol.name, + id: memberSymbol.name, + content: getContent(memberSymbol), + fileInfo: getFileInfo(memberSymbol, basePath), + location: getLocation(memberSymbol) + }; + + if (memberSymbol.flags & ts.SymbolFlags.Method) { + // NOTE: we use the property name `parameters` here so we don't conflict + // with the `params` property that will be updated by dgeni reading the + // `@param` tags from the docs + memberDoc.parameters = getParameters(typeChecker, memberSymbol); + memberDoc.returnType = getReturnType(typeChecker, memberSymbol); + } + + if (memberSymbol.flags & ts.SymbolFlags.Constructor) { + memberDoc.parameters = getParameters(typeChecker, memberSymbol); + memberDoc.name = 'constructor'; + } + + return memberDoc; + } + + + function getParameters(typeChecker, symbol) { + var declaration = symbol.valueDeclaration || symbol.declarations[0]; + var sourceFile = ts.getSourceFileOfNode(declaration); + if(!declaration.parameters) { + console.log(declaration); + throw 'missing declaration parameters'; + } + var signature = typeChecker.getSignatureFromDeclaration(declaration); + return declaration.parameters.map(function(parameter) { + return getText(sourceFile, parameter).trim(); + }); + } + + function getReturnType(typeChecker, symbol) { + var declaration = symbol.valueDeclaration || symbol.declarations[0]; + var sourceFile = ts.getSourceFileOfNode(declaration); + if(declaration.type) { + var signature = typeChecker.getSignatureFromDeclaration(declaration); + return getText(sourceFile, declaration.type).trim(); + } + } + + + function expandSourceFiles(sourceFiles, basePath) { + var filePaths = []; + sourceFiles.forEach(function(sourcePattern) { + filePaths = filePaths.concat(glob.sync(sourcePattern, { cwd: basePath })); + }); + return filePaths; + } + + + function getText(sourceFile, node) { + return sourceFile.text.substring(node.pos, node.end); + } + + + function getLocation(symbol) { + var node = symbol.valueDeclaration || symbol.declarations[0]; + var sourceFile = ts.getSourceFileOfNode(node); + var location = { + start: ts.getLineAndCharacterOfPosition(sourceFile, node.pos), + end: ts.getLineAndCharacterOfPosition(sourceFile, node.end) + }; + return location; + } + +}; + +function insertSorted(collection, item, property) { + var index = collection.length; + while(index>0) { + if(collection[index-1][property] < item[property]) break; + index -= 1; + } + collection.splice(index, 0, item); +} diff --git a/docs/dgeni-package/services/tsParser.js b/docs/dgeni-package/services/tsParser.js new file mode 100644 index 0000000000..e21d2947f8 --- /dev/null +++ b/docs/dgeni-package/services/tsParser.js @@ -0,0 +1,68 @@ +var ts = require('typescript'); + +module.exports = function tsParser(createCompilerHost, log) { + + return { + + options: { + allowNonTsExtensions: true + }, + + parse: function(fileNames, baseDir) { + // This is the easiest way I could find to ensure that we loaded + // modules with paths relative to the baseDir + process.chdir(baseDir); + + // "Compile" a program from the given module filenames, to get hold of a + // typeChecker that can be used to interrogate the modules, exports and so on. + var host = createCompilerHost(this.options); + var program = ts.createProgram(fileNames, this.options, host); + var typeChecker = program.getTypeChecker(); + + // Create an array of module symbols for each file we were given + var moduleSymbols = []; + fileNames.forEach(function(fileName) { + var sourceFile = program.getSourceFile(fileName); + + if (!sourceFile) { + throw new Error('Invalid source file: ' + fileName); + } else if (!sourceFile.symbol) { + // Some files contain only a comment and no actual module code + log.warn('No module code found in ' + fileName); + } else { + moduleSymbols.push(sourceFile.symbol); + } + }); + + + moduleSymbols.forEach(function(tsModule) { + + // The type checker has a nice helper function that returns an array of Symbols + // representing the exports for a given module + tsModule.exportArray = typeChecker.getExportsOfModule(tsModule); + + // Although 'star' imports (e.g. `export * from 'some/module';) get resolved automatically + // by the compiler/binder, it seems that explicit imports (e.g. `export {SomeClass} from 'some/module'`) + // do not so we have to do a little work. + tsModule.exportArray.forEach(function(moduleExport) { + if (moduleExport.flags & 8388608 /* Alias */) { + // To maintain the alias information (particularly the alias name) + // we just attach the original "resolved" symbol to the alias symbol + moduleExport.resolvedSymbol = typeChecker.getAliasedSymbol(moduleExport); + } + }); + }); + + moduleSymbols.typeChecker = typeChecker; + + return { + moduleSymbols: moduleSymbols, + typeChecker: typeChecker, + program: program, + host: host + }; + } + }; + + +}; diff --git a/docs/dgeni-package/services/tsParser.spec.js b/docs/dgeni-package/services/tsParser.spec.js new file mode 100644 index 0000000000..a7819276ca --- /dev/null +++ b/docs/dgeni-package/services/tsParser.spec.js @@ -0,0 +1,21 @@ +var mockPackage = require('../mocks/mockPackage'); +var Dgeni = require('dgeni'); +var path = require('canonical-path'); + +describe('tsParser', function() { + var dgeni, injector, parser; + + beforeEach(function() { + dgeni = new Dgeni([mockPackage()]); + injector = dgeni.configureInjector(); + parser = injector.get('tsParser'); + }); + + it("should parse a TS file", function() { + var parseInfo = parser.parse(['testSrc.ts'], path.resolve(__dirname, '../mocks/')); + var tsModules = parseInfo.moduleSymbols; + expect(tsModules.length).toEqual(1); + expect(tsModules[0].exportArray.length).toEqual(3); + expect(tsModules[0].exportArray.map(function(i) { return i.name; })).toEqual(['MyClass', 'myFn', 'x']); + }); +}); \ No newline at end of file diff --git a/docs/dgeni-package/services/tsParser/createCompilerHost.js b/docs/dgeni-package/services/tsParser/createCompilerHost.js new file mode 100644 index 0000000000..61a19d2804 --- /dev/null +++ b/docs/dgeni-package/services/tsParser/createCompilerHost.js @@ -0,0 +1,32 @@ +var ts = require('typescript'); + +// These are the extension that we should consider when trying to load a module +var extensions = ['.ts', '.js', '.es6'] + +// We need to provide our own version of CompilerHost because, at the moment, there is +// a mix of `.ts`, `.es6` and `.js` (atScript) files in the project and the TypeScript +// compiler only looks for `.ts` files when trying to load imports. +module.exports = function createCompilerHost(log) { + return function createCompilerHost(options) { + + var host = ts.createCompilerHost(options); + + // Override the `getSourceFile` implementation to also look for js and es6 files + var getSourceFile = host.getSourceFile; + host.getSourceFile = function(filename, languageVersion, onError) { + // Iterate through each possible extension and return the first source file that is actually found + for(var i=0; i{$ doc.name $} class

exported from {$ doc.moduleDoc.id $}
-defined in {$ doc.location.start.source.name $}.js (line {$ doc.location.start.line $})

+defined in + {$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $})

{$ doc.description | marked $}

{%- if doc.constructorDoc or doc.members.length -%} diff --git a/docs/public-docs-package/index.js b/docs/public-docs-package/index.js index 311335df50..484022d33f 100644 --- a/docs/public-docs-package/index.js +++ b/docs/public-docs-package/index.js @@ -3,6 +3,25 @@ var basePackage = require('../dgeni-package'); module.exports = new Package('angular-public', [basePackage]) +.config(function(readTypeScriptModules) { + readTypeScriptModules.sourceFiles = [ + 'angular2/annotations.js', + 'angular2/change_detection.ts', + 'angular2/core.js', + 'angular2/di.ts', + 'angular2/directives.js', + 'angular2/forms.js', + 'angular2/router.js', + 'angular2/test.js', + 'angular2/pipes.js' + ]; + readTypeScriptModules.hidePrivateMembers = true; +}) + +.config(function(getLinkInfo) { + getLinkInfo.useFirstAmbiguousLink = false; +}) + // Configure file writing .config(function(writeFilesProcessor) { writeFilesProcessor.outputFolder = 'dist/public_docs';