From 3d997d299431ef47733f7bacef40ac2676d244bf Mon Sep 17 00:00:00 2001 From: Jay Traband Date: Tue, 15 Sep 2015 23:21:55 -0700 Subject: [PATCH] Initial pass at moving api doc builder over to angular.io --- gulpfile.js | 22 + package.json | 10 +- .../api-builder/angular.io-package/index.js | 70 +++ .../angular.io-package/mocks/mockPackage.js | 8 + .../processors/addJadeDataDocsProcessor.js | 77 ++++ .../addJadeDataDocsProcessor.spec.js | 35 ++ .../rendering/trimBlankLines.js | 12 + .../services/packageInfo.js | 14 + .../services/renderMarkdown.js | 54 +++ .../templates/class.template.html | 62 +++ .../templates/enum.template.html | 1 + .../templates/function.template.html | 22 + .../templates/jade-data.template.html | 9 + .../templates/layout/base.template.html | 1 + .../templates/lib/paramList.html | 12 + .../templates/module.template.html | 15 + .../templates/var.template.html | 12 + public/api-builder/docs-package/index.js | 123 ++++++ .../docs-package/mocks/importedSrc.ts | 1 + .../docs-package/mocks/mockPackage.js | 10 + .../api-builder/docs-package/mocks/testSrc.ts | 34 ++ .../processors/checkUnbalancedBackTicks.js | 28 ++ .../convertPrivateClassesToInterfaces.js | 10 + .../processors/createOverviewDump.js | 24 ++ .../processors/extractTitleFromGuides.js | 24 ++ .../processors/generateNavigationDoc.js | 68 +++ .../api-builder/docs-package/readers/ngdoc.js | 32 ++ .../docs-package/readers/ngdoc.spec.js | 45 ++ .../templates/class.template.html | 44 ++ .../templates/common.template.html | 9 + .../templates/const.template.html | 1 + .../templates/data-module.template.js | 3 + .../templates/function.template.html | 11 + .../templates/guide.template.html | 5 + .../templates/interface.template.html | 1 + .../templates/layout/base.template.html | 1 + .../templates/lib/githubLinks.html | 3 + .../docs-package/templates/lib/paramList.html | 7 + .../templates/module.template.html | 19 + .../templates/overview-dump.template.html | 43 ++ .../templates/type-alias.template.html | 10 + .../docs-package/templates/var.template.html | 10 + public/api-builder/links-package/index.js | 12 + .../links-package/inline-tag-defs/link.js | 33 ++ .../links-package/services/getLinkInfo.js | 72 ++++ .../api-builder/public-docs-package/index.js | 24 ++ .../api-builder/typescript-package/index.js | 70 +++ .../typescript-package/mocks/mockPackage.js | 11 + .../ignoreExportsMatching.ts | 4 + .../mocks/readTypeScriptModules/interfaces.ts | 5 + .../orderingOfMembers.ts | 6 + .../mocks/tsParser/importedSrc.ts | 1 + .../mocks/tsParser/testSrc.ts | 34 ++ .../processors/readTypeScriptModules.js | 401 ++++++++++++++++++ .../processors/readTypeScriptModules.spec.js | 115 +++++ .../convertPrivateClassesToInterfaces.js | 31 ++ .../convertPrivateClassesToInterfaces.spec.js | 76 ++++ .../typescript-package/services/modules.js | 3 + .../services/tsParser/createCompilerHost.js | 62 +++ .../tsParser/createCompilerHost.spec.js | 80 ++++ .../services/tsParser/getContent.js | 49 +++ .../services/tsParser/getExportDocType.js | 54 +++ .../services/tsParser/getFileInfo.js | 20 + .../services/tsParser/index.js | 74 ++++ .../services/tsParser/index.spec.js | 21 + 65 files changed, 2259 insertions(+), 1 deletion(-) create mode 100644 public/api-builder/angular.io-package/index.js create mode 100644 public/api-builder/angular.io-package/mocks/mockPackage.js create mode 100644 public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.js create mode 100644 public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.spec.js create mode 100644 public/api-builder/angular.io-package/rendering/trimBlankLines.js create mode 100644 public/api-builder/angular.io-package/services/packageInfo.js create mode 100644 public/api-builder/angular.io-package/services/renderMarkdown.js create mode 100644 public/api-builder/angular.io-package/templates/class.template.html create mode 100644 public/api-builder/angular.io-package/templates/enum.template.html create mode 100644 public/api-builder/angular.io-package/templates/function.template.html create mode 100644 public/api-builder/angular.io-package/templates/jade-data.template.html create mode 100644 public/api-builder/angular.io-package/templates/layout/base.template.html create mode 100644 public/api-builder/angular.io-package/templates/lib/paramList.html create mode 100644 public/api-builder/angular.io-package/templates/module.template.html create mode 100644 public/api-builder/angular.io-package/templates/var.template.html create mode 100644 public/api-builder/docs-package/index.js create mode 100644 public/api-builder/docs-package/mocks/importedSrc.ts create mode 100644 public/api-builder/docs-package/mocks/mockPackage.js create mode 100644 public/api-builder/docs-package/mocks/testSrc.ts create mode 100644 public/api-builder/docs-package/processors/checkUnbalancedBackTicks.js create mode 100644 public/api-builder/docs-package/processors/convertPrivateClassesToInterfaces.js create mode 100644 public/api-builder/docs-package/processors/createOverviewDump.js create mode 100644 public/api-builder/docs-package/processors/extractTitleFromGuides.js create mode 100644 public/api-builder/docs-package/processors/generateNavigationDoc.js create mode 100644 public/api-builder/docs-package/readers/ngdoc.js create mode 100644 public/api-builder/docs-package/readers/ngdoc.spec.js create mode 100644 public/api-builder/docs-package/templates/class.template.html create mode 100644 public/api-builder/docs-package/templates/common.template.html create mode 100644 public/api-builder/docs-package/templates/const.template.html create mode 100644 public/api-builder/docs-package/templates/data-module.template.js create mode 100644 public/api-builder/docs-package/templates/function.template.html create mode 100644 public/api-builder/docs-package/templates/guide.template.html create mode 100644 public/api-builder/docs-package/templates/interface.template.html create mode 100644 public/api-builder/docs-package/templates/layout/base.template.html create mode 100644 public/api-builder/docs-package/templates/lib/githubLinks.html create mode 100644 public/api-builder/docs-package/templates/lib/paramList.html create mode 100644 public/api-builder/docs-package/templates/module.template.html create mode 100644 public/api-builder/docs-package/templates/overview-dump.template.html create mode 100644 public/api-builder/docs-package/templates/type-alias.template.html create mode 100644 public/api-builder/docs-package/templates/var.template.html create mode 100644 public/api-builder/links-package/index.js create mode 100644 public/api-builder/links-package/inline-tag-defs/link.js create mode 100644 public/api-builder/links-package/services/getLinkInfo.js create mode 100644 public/api-builder/public-docs-package/index.js create mode 100644 public/api-builder/typescript-package/index.js create mode 100644 public/api-builder/typescript-package/mocks/mockPackage.js create mode 100644 public/api-builder/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts create mode 100644 public/api-builder/typescript-package/mocks/readTypeScriptModules/interfaces.ts create mode 100644 public/api-builder/typescript-package/mocks/readTypeScriptModules/orderingOfMembers.ts create mode 100644 public/api-builder/typescript-package/mocks/tsParser/importedSrc.ts create mode 100644 public/api-builder/typescript-package/mocks/tsParser/testSrc.ts create mode 100644 public/api-builder/typescript-package/processors/readTypeScriptModules.js create mode 100644 public/api-builder/typescript-package/processors/readTypeScriptModules.spec.js create mode 100644 public/api-builder/typescript-package/services/convertPrivateClassesToInterfaces.js create mode 100644 public/api-builder/typescript-package/services/convertPrivateClassesToInterfaces.spec.js create mode 100644 public/api-builder/typescript-package/services/modules.js create mode 100644 public/api-builder/typescript-package/services/tsParser/createCompilerHost.js create mode 100644 public/api-builder/typescript-package/services/tsParser/createCompilerHost.spec.js create mode 100644 public/api-builder/typescript-package/services/tsParser/getContent.js create mode 100644 public/api-builder/typescript-package/services/tsParser/getExportDocType.js create mode 100644 public/api-builder/typescript-package/services/tsParser/getFileInfo.js create mode 100644 public/api-builder/typescript-package/services/tsParser/index.js create mode 100644 public/api-builder/typescript-package/services/tsParser/index.spec.js diff --git a/gulpfile.js b/gulpfile.js index 25d64440d9..c53289b1a6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,6 +9,10 @@ var Git = require("nodegit"); var argv = require('yargs').argv; var Q = require("q"); var Minimatch = require("minimatch").Minimatch; +var Dgeni = require('dgeni'); +var fsExtra = require('fs-extra'); +var fs = fsExtra; + var docShredder = require('./public/doc-shredder/doc-shredder'); @@ -116,6 +120,24 @@ gulp.task('git-changed-examples', ['shred-full'], function(){ }); }); + +gulp.task('build-api-docs', function() { + var fs = require('fs-extra'); + if (!fs.existsSync('../angular')) { + throw new Error('build-api-docs task requires the angular2 repo to be at ' + path.resolve('../angular')); + } + try { + var dgeni = new Dgeni([require('./public/api-builder/angular.io-package')]); + return dgeni.generate(); + } catch(x) { + console.log(x); + console.log(x.stack); + throw x; + } +}); + + + function filterOutExcludedPatterns(fileNames, excludeMatchers) { return fileNames.filter(function(fileName) { return !excludeMatchers.some(function(excludeMatcher) { diff --git a/package.json b/package.json index 7e277e3d86..b5b29af132 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { - "type": "git" + "type": "git", + "url": "https://github.com/angular/angular.io.git" }, "licenses": [ { @@ -25,19 +26,26 @@ "del": "^1.2.0", "dgeni": "^0.4.0", "dgeni-packages": "^0.10.0", + "fs-extra": "^0.24.0", + "glob": "^5.0.14", "gulp": "^3.5.6", "gulp-task-listing": "^1.0.1", "gulp-util": "^3.0.6", "gulp-watch": "^4.3.4", + "html2jade": "^0.8.4", + "indent-string": "^2.1.0", "jasmine-core": "^2.3.4", "karma": "^0.13.8", "karma-chrome-launcher": "^0.2.0", "karma-jasmine": "^0.3.6", "lodash": "^3.10.1", + "marked": "^0.3.5", "minimatch": "^2.0.10", + "node-html-encoder": "0.0.2", "nodegit": "^0.4.1", "path": "^0.11.14", "q": "^1.4.1", + "typescript": "^1.5.3", "yargs": "^3.23.0" }, "contributors": [ diff --git a/public/api-builder/angular.io-package/index.js b/public/api-builder/angular.io-package/index.js new file mode 100644 index 0000000000..02664bd764 --- /dev/null +++ b/public/api-builder/angular.io-package/index.js @@ -0,0 +1,70 @@ +var path = require('canonical-path'); +var Package = require('dgeni').Package; +var basePackage = require('../public-docs-package'); + +var PARTIAL_PATH = 'partials'; +var MODULES_DOCS_PATH = PARTIAL_PATH + '/api'; + +// OLD paths +// var DOCS_DIST = 'dist/angular.io/partials/api/angular2/'; +// var DOCS_IO_DIST = '../angular.io/public/docs/js/latest/api/'; + +module.exports = new Package('angular.io', [basePackage]) + +.factory(require('./services/renderMarkdown')) +.processor(require('./processors/addJadeDataDocsProcessor')) + // overrides base packageInfo and returns the one for the 'angular/angular' repo. +.factory(require('./services/packageInfo')) + +// Configure rendering +.config(function(templateFinder, templateEngine) { + + templateFinder.templateFolders + .unshift(path.resolve(__dirname, 'templates')); +}) + +.config(function(writeFilesProcessor) { + writeFilesProcessor.outputFolder = 'dist/angular.io'; +}) + +.config(function(readFilesProcessor, generateNavigationDoc, createOverviewDump) { + // Clear out unwanted processors + readFilesProcessor.$enabled = false; + generateNavigationDoc.$enabled = false; + createOverviewDump.$enabled = false; +}) + + + + +.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) { + + computePathsProcessor.pathTemplates.push({ + docTypes: ['module'], + pathTemplate: '${id}/', + outputPathTemplate: MODULES_DOCS_PATH + '/${id}/index.jade' + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: EXPORT_DOC_TYPES, + pathTemplate: '${moduleDoc.id}/${name}-${docType}.html', + outputPathTemplate: MODULES_DOCS_PATH + '/${moduleDoc.id}/${name}-${docType}.jade', + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: ['jade-data'], + pathTemplate: '${originalDoc.id}/_data', + outputPathTemplate: MODULES_DOCS_PATH + '/${path}.json' + }); +}) + +.config(function(getLinkInfo) { + getLinkInfo.relativeLinks = true; +}) + + +.config(function(templateEngine, getInjectables) { + templateEngine.filters = templateEngine.filters.concat(getInjectables([require('./rendering/trimBlankLines')])); +}); + + diff --git a/public/api-builder/angular.io-package/mocks/mockPackage.js b/public/api-builder/angular.io-package/mocks/mockPackage.js new file mode 100644 index 0000000000..473bc36adb --- /dev/null +++ b/public/api-builder/angular.io-package/mocks/mockPackage.js @@ -0,0 +1,8 @@ +var Package = require('dgeni').Package; + +module.exports = function mockPackage() { + + return new Package('mockPackage', [require('../')]) + + .factory('log', function() { return require('dgeni/lib/mocks/log')(false); }) +}; diff --git a/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.js b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.js new file mode 100644 index 0000000000..9ae336c469 --- /dev/null +++ b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.js @@ -0,0 +1,77 @@ +var _ = require('lodash'); +var path = require('canonical-path'); + +var titleCase = function(text) { + return text.replace(/(.)(.*)/, function(_, first, rest) { + return first.toUpperCase() + rest; + }); +}; + +/* +* Create _data.json file for Harp pages +* +* http://harpjs.com/docs/development/metadata +* +* This method creates the meta data required for each page +* such as the title, description, etc. This meta data is used +* in the harp static site generator to create the title for headers +* and the navigation used in the API docs +* +*/ + +module.exports = function addJadeDataDocsProcessor() { + return { + $runAfter: ['adding-extra-docs'], + $runBefore: ['extra-docs-added'], + $process: function(docs) { + var extraDocs = []; + var modules = []; + + + /* + * Create Data for Modules + * + * Modules must be public and have content + */ + + _.forEach(docs, function(doc) { + if (doc.docType === 'module' && !doc.private && doc.exports.length) { + modules.push(doc); + + // GET DATA FOR INDEX PAGE OF MODULE SECTION + var indexPageInfo = [{ + name: 'index', + title: _.map(path.basename(doc.fileInfo.baseName).split('_'), function(part) { + return titleCase(part); + }).join(' '), + intro: doc.description.replace('"', '\"').replace(/\s*(\r?\n|\r)\s*/g," ") + }]; + + // GET DATA FOR EACH PAGE (CLASS, VARS, FUNCTIONS) + var modulePageInfo = _.map(doc.exports, function(exportDoc) { + return { + name: exportDoc.name + '-' + exportDoc.docType, + title: exportDoc.name, + varType: exportDoc.symbolTypeName && titleCase(exportDoc.symbolTypeName) + }; + }); + + //COMBINE PAGE DATA + var allPageData = indexPageInfo.concat(modulePageInfo); + + // PUSH DATA TO EXTRA DOCS ARRAY + extraDocs.push({ + id: doc.id + "-data", + aliases: [doc.id + "-data"], + docType: 'jade-data', + originalDoc: doc, + data: allPageData + }); + } + }); + + + return docs.concat(extraDocs); + } + }; +}; diff --git a/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.spec.js b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.spec.js new file mode 100644 index 0000000000..e0e4c5965d --- /dev/null +++ b/public/api-builder/angular.io-package/processors/addJadeDataDocsProcessor.spec.js @@ -0,0 +1,35 @@ +var mockPackage = require('../mocks/mockPackage'); +var Dgeni = require('dgeni'); + +describe('addJadeDataDocsProcessor', function() { + var dgeni, injector, processor; + + beforeEach(function() { + dgeni = new Dgeni([mockPackage()]); + injector = dgeni.configureInjector(); + processor = injector.get('addJadeDataDocsProcessor'); + }); + + it('should add a doc for each module', function() { + var docs = [ + { + docType: 'module', + id: 'someModule', + exports: [{ name: 'MyClass', docType: 'class'}], + fileInfo: { baseName: 'x_y' }, + description: 'some description\nsecond line' + } + ]; + docs = processor.$process(docs); + + expect(docs[1]).toEqual({ + id : 'someModule-data', + aliases : [ 'someModule-data' ], + docType : 'jade-data', + originalDoc : docs[0], + data : [ + { name : 'index', title : 'X Y', intro : 'some description second line' }, + { name : 'MyClass-class', title : 'MyClass', varType : undefined } + ] }); + }); +}); diff --git a/public/api-builder/angular.io-package/rendering/trimBlankLines.js b/public/api-builder/angular.io-package/rendering/trimBlankLines.js new file mode 100644 index 0000000000..82728d7635 --- /dev/null +++ b/public/api-builder/angular.io-package/rendering/trimBlankLines.js @@ -0,0 +1,12 @@ +module.exports = function(encodeCodeBlock) { + return { + name: 'trimBlankLines', + process: function(str) { + var lines = str.split(/\r?\n/); + while(lines[0] === '') { + lines.shift(); + } + return lines.join('\n'); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/angular.io-package/services/packageInfo.js b/public/api-builder/angular.io-package/services/packageInfo.js new file mode 100644 index 0000000000..1cd06db3c0 --- /dev/null +++ b/public/api-builder/angular.io-package/services/packageInfo.js @@ -0,0 +1,14 @@ +'use strict'; + +var fs = require('fs'); +var path = require('canonical-path'); + +/** + * Load information about this project from the package.json + * @return {Object} The package information + */ +module.exports = function packageInfo() { + + var topLevelPackageJson= path.join('../angular','package.json'); + return JSON.parse(fs.readFileSync(topLevelPackageJson), 'UTF-8'); +} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/services/renderMarkdown.js b/public/api-builder/angular.io-package/services/renderMarkdown.js new file mode 100644 index 0000000000..74d4ee1ac8 --- /dev/null +++ b/public/api-builder/angular.io-package/services/renderMarkdown.js @@ -0,0 +1,54 @@ +var marked = require('marked'); +var Encoder = require('node-html-encoder').Encoder; +var html2jade = require('html2jade'); +var indentString = require('indent-string'); + + +function stripTags(s) { //from sugar.js + return s.replace(RegExp('<\/?[^<>]*>', 'gi'), ''); +} + +// entity type encoder +var encoder = new Encoder('entity'); + + + +/** + * @dgService renderMarkdown + * @description + * Render the markdown in the given string as HTML. + */ +module.exports = function renderMarkdown(trimIndentation) { + + var renderer = new marked.Renderer(); + + renderer.code = function(code, lang, escaped) { + + var cssClasses = ['prettyprint', 'linenums']; + var trimmedCode = trimIndentation(code); + + if(lang) { + if(lang=='html') { + trimmedCode = encoder.htmlEncode(trimmedCode); + } + cssClasses.push(this.options.langPrefix + escape(lang, true)); + } + + return 'pre(class="' + cssClasses.join(' ') + '")\n' + indentString('code.\n', ' ', 2) + trimmedCode; + }; + + renderer.heading = function (text, level, raw) { + var headingText = marked.Renderer.prototype.heading.call(renderer, text, level, raw); + var title = 'h2 ' + stripTags(headingText); + + if (level==2) { + title = '.l-main-section\n' + indentString(title, ' ', 2) ; + } + + return title; + }; + + return function(content) { + return marked(content, { renderer: renderer }); + }; +}; \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/class.template.html b/public/api-builder/angular.io-package/templates/class.template.html new file mode 100644 index 0000000000..a51fc5fb08 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/class.template.html @@ -0,0 +1,62 @@ +{% include "lib/githubLinks.html" -%} +{% include "lib/paramList.html" -%} +{% extends 'layout/base.template.html' -%} + +{% block body %} +p.location-badge. + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ githubViewLink(doc) $} + +:markdown +{$ doc.moduleDoc.description | indent(2, true) | trimBlankLines $} + +{%- if doc.decorators %} +.l-main-section + h2 Annotations +{%- for decorator in doc.decorators %} + .l-sub-section + h3.annotation {$ decorator.name $} + pre.prettyprint + code. + @{$ decorator.name $}{$ paramList(decorator.arguments) | indent(8, false) $} +{% endfor %} +{% endif -%} + + +{%- if doc.constructorDoc or doc.members.length -%} +.l-main-section + h2 Members + +{%- if doc.constructorDoc %} + .l-sub-section + h3 {$ doc.constructorDoc.name $} + + {% if doc.constructorDoc.parameters %} + pre.prettyprint + code. + {$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $} + {% endif %} + :markdown +{$ doc.constructorDoc.description | indent(6, true) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $} + + +{% endif -%} + +{%- for member in doc.members %}{% if not member.private %} + .l-sub-section + h3 {$ member.name $}{% if member.optional %}?{% endif %} + + {% if member.parameters %} + pre.prettyprint + code. + {$ member.name $}{$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(doc.returnType) $} + {% endif %} + :markdown +{$ member.description | indent(6, true) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $} + + + +{% endif %}{% endfor %} +{%- endif -%} + +{% endblock %} diff --git a/public/api-builder/angular.io-package/templates/enum.template.html b/public/api-builder/angular.io-package/templates/enum.template.html new file mode 100644 index 0000000000..9c59159b29 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/enum.template.html @@ -0,0 +1 @@ +{% extends 'class.template.html' -%} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/function.template.html b/public/api-builder/angular.io-package/templates/function.template.html new file mode 100644 index 0000000000..5136a35021 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/function.template.html @@ -0,0 +1,22 @@ +{% include "lib/githubLinks.html" -%} +{% include "lib/paramList.html" -%} +{% extends 'layout/base.template.html' -%} + +{% block body %} +.l-main-section + h2(class="function export") {$ doc.name $} + + {% if doc.parameters %} + pre.prettyprint + code. + {$ doc.name $}{$ paramList(doc.parameters) | indent(4, true) | trim $}{$ returnType(doc.returnType) $} + {% endif %} + + p.location-badge. + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ githubViewLink(doc) $} + + :markdown +{$ doc.description | indent(4, true) | trimBlankLines $} + +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/jade-data.template.html b/public/api-builder/angular.io-package/templates/jade-data.template.html new file mode 100644 index 0000000000..3cefa7980e --- /dev/null +++ b/public/api-builder/angular.io-package/templates/jade-data.template.html @@ -0,0 +1,9 @@ +{ +{%- for item in doc.data %} + "{$ item.name $}" : { + "title" : "{$ item.title $}"{% if item.intro %}, + "intro" : "{$ item.intro $}"{% endif %}{% if item.varType %}, + "varType" : "{$ item.varType $}"{% endif %} + }{% if not loop.last %},{% endif %} +{% endfor -%} +} diff --git a/public/api-builder/angular.io-package/templates/layout/base.template.html b/public/api-builder/angular.io-package/templates/layout/base.template.html new file mode 100644 index 0000000000..16a0d9dc96 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/layout/base.template.html @@ -0,0 +1 @@ +{% block body %}{% endblock %} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/lib/paramList.html b/public/api-builder/angular.io-package/templates/lib/paramList.html new file mode 100644 index 0000000000..24ba12c080 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/lib/paramList.html @@ -0,0 +1,12 @@ +{% macro paramList(params) -%} + {%- if params -%} + ({%- for param in params -%} + {$ param | escape $}{% if not loop.last %}, {% endif %} + {%- endfor %}) + {%- endif %} +{%- endmacro -%} + + +{% macro returnType(returnType) -%} + {%- if returnType %} : {$ returnType | escape $}{% endif -%} +{%- endmacro -%} diff --git a/public/api-builder/angular.io-package/templates/module.template.html b/public/api-builder/angular.io-package/templates/module.template.html new file mode 100644 index 0000000000..3fbe568783 --- /dev/null +++ b/public/api-builder/angular.io-package/templates/module.template.html @@ -0,0 +1,15 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' -%} +{% block body -%} +p.location-badge. + defined in {$ githubViewLink(doc) $} + +ul + for page, slug in public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]]._data + if slug != 'index' + - var url = "/docs/" + current.path[1] + "/" + current.path[2] + "/" + current.path[3] + "/" + current.path[4] + "/" + slug + ".html" + + li.c8 + != partial("../../../../../_includes/_hover-card", {name: page.title, url: url }) + +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/angular.io-package/templates/var.template.html b/public/api-builder/angular.io-package/templates/var.template.html new file mode 100644 index 0000000000..72aa66ecfd --- /dev/null +++ b/public/api-builder/angular.io-package/templates/var.template.html @@ -0,0 +1,12 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' %} + +{% block body %} +.l-main-section + p.location-badge. + exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } + defined in {$ githubViewLink(doc) $} + + :markdown +{$ doc.description | indent(4, true) | trimBlankLines $} +{% endblock %} diff --git a/public/api-builder/docs-package/index.js b/public/api-builder/docs-package/index.js new file mode 100644 index 0000000000..b456ea6b3d --- /dev/null +++ b/public/api-builder/docs-package/index.js @@ -0,0 +1,123 @@ +var Package = require('dgeni').Package; +var jsdocPackage = require('dgeni-packages/jsdoc'); +var nunjucksPackage = require('dgeni-packages/nunjucks'); +var typescriptPackage = require('../typescript-package'); +var linksPackage = require('../links-package'); +var gitPackage = require('dgeni-packages/git'); +var path = require('canonical-path'); +var fs = require('fs'); + +// Define the dgeni package for generating the docs +module.exports = new Package('angular-v2-docs', [jsdocPackage, nunjucksPackage, typescriptPackage, linksPackage, gitPackage]) + +// Register the services and file readers +.factory(require('./readers/ngdoc')) + +// Register the processors +.processor(require('./processors/convertPrivateClassesToInterfaces')) +.processor(require('./processors/generateNavigationDoc')) +.processor(require('./processors/extractTitleFromGuides')) +.processor(require('./processors/createOverviewDump')) +.processor(require('./processors/checkUnbalancedBackTicks')) + +// Configure the log service +.config(function(log) { + log.level = 'info'; +}) + + +.config(function(renderDocsProcessor, versionInfo) { + renderDocsProcessor.extraData.versionInfo = versionInfo; +}) + +// Configure file reading +.config(function(readFilesProcessor, ngdocFileReader, readTypeScriptModules) { + readFilesProcessor.fileReaders = [ngdocFileReader]; + var angular_repo_path = path.resolve(__dirname, '../../../../angular'); + // TODO: confirm that the angular repo is actually there. + if (!fs.existsSync(angular_repo_path)) { + throw new Error('build-api-docs task requires the angular2 repo to be at ' + angular_repo_path); + } + readFilesProcessor.basePath = angular_repo_path; + readFilesProcessor.sourceFiles = [ + { include: 'modules/*/docs/**/*.md', basePath: 'modules' }, + { include: 'docs/content/**/*.md', basePath: 'docs/content' } + ]; + + readTypeScriptModules.sourceFiles = [ + '*/*.@(js|es6|ts)', + '*/src/**/*.@(js|es6|ts)' + ]; + readTypeScriptModules.basePath = path.resolve(readFilesProcessor.basePath, 'modules'); +}) + + +.config(function(parseTagsProcessor, getInjectables) { + // 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.docProperty = 'paramData'; + tagDef.transforms = []; + } + }); + +}) + + +// Configure links +.config(function(getLinkInfo) { + getLinkInfo.useFirstAmbiguousLink = true; +}) + + +// Configure file writing +.config(function(writeFilesProcessor) { + writeFilesProcessor.outputFolder = 'dist/docs'; +}) + + +// Configure rendering +.config(function(templateFinder, templateEngine) { + + // Nunjucks and Angular conflict in their template bindings so change Nunjucks + templateEngine.config.tags = { + variableStart: '{$', + variableEnd: '$}' + }; + + templateFinder.templateFolders + .unshift(path.resolve(__dirname, 'templates')); + + templateFinder.templatePatterns = [ + '${ doc.template }', + '${ doc.id }.${ doc.docType }.template.html', + '${ doc.id }.template.html', + '${ doc.docType }.template.html', + 'common.template.html' + ]; +}) + + +// Configure ids and paths +.config(function(computeIdsProcessor, computePathsProcessor) { + + computeIdsProcessor.idTemplates.push({ + docTypes: ['guide'], + getId: function(doc) { + return doc.fileInfo.relativePath + // path should be relative to `modules` folder + .replace(/.*\/?modules\//, '') + // path should not include `/docs/` + .replace(/\/docs\//, '/') + // path should not have a suffix + .replace(/\.\w*$/, ''); + }, + getAliases: function(doc) { return [doc.id]; } + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: ['guide'], + pathTemplate: '/${id}', + outputPathTemplate: 'partials/guides/${id}.html' + }); +}); diff --git a/public/api-builder/docs-package/mocks/importedSrc.ts b/public/api-builder/docs-package/mocks/importedSrc.ts new file mode 100644 index 0000000000..74a115e5fe --- /dev/null +++ b/public/api-builder/docs-package/mocks/importedSrc.ts @@ -0,0 +1 @@ +export var x = 100; \ No newline at end of file diff --git a/public/api-builder/docs-package/mocks/mockPackage.js b/public/api-builder/docs-package/mocks/mockPackage.js new file mode 100644 index 0000000000..61d0df228a --- /dev/null +++ b/public/api-builder/docs-package/mocks/mockPackage.js @@ -0,0 +1,10 @@ +var Package = require('dgeni').Package; + +module.exports = function mockPackage() { + + return new Package('mockPackage', [require('../')]) + + // provide a mock log service + .factory('log', function() { return require('dgeni/lib/mocks/log')(false); }); + +}; diff --git a/public/api-builder/docs-package/mocks/testSrc.ts b/public/api-builder/docs-package/mocks/testSrc.ts new file mode 100644 index 0000000000..7b6d3f18dd --- /dev/null +++ b/public/api-builder/docs-package/mocks/testSrc.ts @@ -0,0 +1,34 @@ +/** + * @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/public/api-builder/docs-package/processors/checkUnbalancedBackTicks.js b/public/api-builder/docs-package/processors/checkUnbalancedBackTicks.js new file mode 100644 index 0000000000..2d359c7785 --- /dev/null +++ b/public/api-builder/docs-package/processors/checkUnbalancedBackTicks.js @@ -0,0 +1,28 @@ +var _ = require('lodash'); + +/** + * @dgProcessor checkUnbalancedBackTicks + * @description + * Searches the rendered content for an odd number of (```) backticks, + * which would indicate an unbalanced pair and potentially a typo in the + * source content. + */ +module.exports = function checkUnbalancedBackTicks(log, createDocMessage) { + + var BACKTICK_REGEX = /^ *```/gm; + + return { + $runAfter: ['checkAnchorLinksProcessor'], + $process: function(docs) { + _.forEach(docs, function(doc) { + if ( doc.renderedContent ) { + var matches = doc.renderedContent.match(BACKTICK_REGEX); + if (matches && matches.length % 2 !== 0) { + log.warn(createDocMessage('checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content', doc)); + console.log(doc.renderedContent); + } + } + }); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/convertPrivateClassesToInterfaces.js b/public/api-builder/docs-package/processors/convertPrivateClassesToInterfaces.js new file mode 100644 index 0000000000..6e6e758daa --- /dev/null +++ b/public/api-builder/docs-package/processors/convertPrivateClassesToInterfaces.js @@ -0,0 +1,10 @@ +module.exports = function convertPrivateClassesToInterfacesProcessor(convertPrivateClassesToInterfaces) { + return { + $runAfter: ['processing-docs'], + $runBefore: ['docs-processed'], + $process: function(docs) { + convertPrivateClassesToInterfaces(docs, false); + return docs; + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/createOverviewDump.js b/public/api-builder/docs-package/processors/createOverviewDump.js new file mode 100644 index 0000000000..eadd24e0af --- /dev/null +++ b/public/api-builder/docs-package/processors/createOverviewDump.js @@ -0,0 +1,24 @@ +var _ = require('lodash'); + +module.exports = function createOverviewDump() { + + return { + $runAfter: ['processing-docs'], + $runBefore: ['docs-processed'], + $process: function(docs) { + var overviewDoc = { + id: 'overview-dump', + aliases: ['overview-dump'], + path: 'overview-dump', + outputPath: 'overview-dump.html', + modules: [] + }; + _.forEach(docs, function(doc) { + if ( doc.docType === 'module' ) { + overviewDoc.modules.push(doc); + } + }); + docs.push(overviewDoc); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/extractTitleFromGuides.js b/public/api-builder/docs-package/processors/extractTitleFromGuides.js new file mode 100644 index 0000000000..1a4e6c3806 --- /dev/null +++ b/public/api-builder/docs-package/processors/extractTitleFromGuides.js @@ -0,0 +1,24 @@ +var _ = require('lodash'); + +module.exports = function extractTitleFromGuides() { + + return { + $runAfter: ['processing-docs'], + $runBefore: ['docs-processed'], + $process: function(docs) { + _(docs).forEach(function(doc) { + if (doc.docType === 'guide') { + doc.name = doc.name || getNameFromHeading(doc.description); + } + }); + } + }; +}; + + +function getNameFromHeading(text) { + var match = /^\s*#\s*(.*)/.exec(text); + if (match) { + return match[1]; + } +} \ No newline at end of file diff --git a/public/api-builder/docs-package/processors/generateNavigationDoc.js b/public/api-builder/docs-package/processors/generateNavigationDoc.js new file mode 100644 index 0000000000..5e8ecf7149 --- /dev/null +++ b/public/api-builder/docs-package/processors/generateNavigationDoc.js @@ -0,0 +1,68 @@ +var _ = require('lodash'); + +module.exports = function generateNavigationDoc() { + + return { + $runAfter: ['docs-processed'], + $runBefore: ['rendering-docs'], + $process: function(docs) { + var modulesDoc = { + value: { sections: [] }, + moduleName: 'navigation-modules', + serviceName: 'MODULES', + template: 'data-module.template.js', + outputPath: 'js/navigation-modules.js' + }; + + _.forEach(docs, function(doc) { + if ( doc.docType === 'module' ) { + var moduleNavItem = { + path: doc.path, + partial: doc.outputPath, + name: doc.id, + type: 'module', + pages: [] + }; + + modulesDoc.value.sections.push(moduleNavItem); + + _.forEach(doc.exports, function(exportDoc) { + if (!exportDoc.private) { + var exportNavItem = { + path: exportDoc.path, + partial: exportDoc.outputPath, + name: exportDoc.name, + type: exportDoc.docType + }; + moduleNavItem.pages.push(exportNavItem); + } + }); + } + }); + + docs.push(modulesDoc); + + + var guidesDoc = { + value: { pages: [] }, + moduleName: 'navigation-guides', + serviceName: 'GUIDES', + template: 'data-module.template.js', + outputPath: 'js/navigation-guides.js' + }; + + _.forEach(docs, function(doc) { + if ( doc.docType === 'guide' ) { + var guideDoc = { + path: doc.path, + partial: doc.outputPath, + name: doc.name, + type: 'guide' + }; + guidesDoc.value.pages.push(guideDoc); + } + }); + docs.push(guidesDoc); + } + }; +}; diff --git a/public/api-builder/docs-package/readers/ngdoc.js b/public/api-builder/docs-package/readers/ngdoc.js new file mode 100644 index 0000000000..a1e6a7e4d5 --- /dev/null +++ b/public/api-builder/docs-package/readers/ngdoc.js @@ -0,0 +1,32 @@ +var path = require('canonical-path'); + +/** + * @dgService ngdocFileReader + * @description + * This file reader will pull the contents from a text file (by default .ngdoc) + * + * The doc will initially have the form: + * ``` + * { + * content: 'the content of the file', + * startingLine: 1 + * } + * ``` + */ +module.exports = function ngdocFileReader() { + var reader = { + name: 'ngdocFileReader', + defaultPattern: /\.md$/, + getDocs: function(fileInfo) { + + // We return a single element array because ngdoc files only contain one document + return [{ + docType: 'guide', + content: fileInfo.content, + startingLine: 1 + }]; + } + }; + + return reader; +}; \ No newline at end of file diff --git a/public/api-builder/docs-package/readers/ngdoc.spec.js b/public/api-builder/docs-package/readers/ngdoc.spec.js new file mode 100644 index 0000000000..658663c9fb --- /dev/null +++ b/public/api-builder/docs-package/readers/ngdoc.spec.js @@ -0,0 +1,45 @@ +var ngdocFileReaderFactory = require('./ngdoc'); +var path = require('canonical-path'); + +describe('ngdocFileReader', function() { + + var fileReader; + + var createFileInfo = function(file, content, basePath) { + return { + fileReader: fileReader.name, + filePath: file, + baseName: path.basename(file, path.extname(file)), + extension: path.extname(file).replace(/^\./, ''), + basePath: basePath, + relativePath: path.relative(basePath, file), + content: content + }; + }; + + + beforeEach(function() { + fileReader = ngdocFileReaderFactory(); + }); + + + describe('defaultPattern', function() { + it('should match .md files', function() { + expect(fileReader.defaultPattern.test('abc.md')).toBeTruthy(); + expect(fileReader.defaultPattern.test('abc.js')).toBeFalsy(); + }); + }); + + + describe('getDocs', function() { + it('should return an object containing info about the file and its contents', function() { + var fileInfo = createFileInfo('project/path/modules/someModule/foo/docs/subfolder/bar.ngdoc', 'A load of content', 'project/path'); + expect(fileReader.getDocs(fileInfo)).toEqual([{ + docType: 'guide', + content: 'A load of content', + startingLine: 1 + }]); + }); + }); +}); + diff --git a/public/api-builder/docs-package/templates/class.template.html b/public/api-builder/docs-package/templates/class.template.html new file mode 100644 index 0000000000..a015cf5e7e --- /dev/null +++ b/public/api-builder/docs-package/templates/class.template.html @@ -0,0 +1,44 @@ +{% include "lib/paramList.html" -%} +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' -%} + +{% block body %} +

{$ doc.name $} {$ doc.docType $}

+

exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $} +

+

{$ doc.description | marked $}

+ +{%- if doc.decorators %} +

Annotations

+{%- for decorator in doc.decorators %} +

@{$ decorator.name $}{$ paramList(decorator.arguments) $}

+{% endfor %} +{% endif -%} + +{%- if doc.constructorDoc or doc.members.length -%} +

Members

+ +{%- if doc.constructorDoc %} +
+

{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.params) $}

+ {% marked %} + {$ doc.constructorDoc.description $} + {% endmarked %} +
+{% endif -%} + +{%- for member in doc.members %}{% if not member.private %} +
+

+ {$ member.name $}{% if member.optional %}?{% endif %}{$ paramList(member.params) $} +

+ {% marked %} + {$ member.description $} + {% endmarked %} +
+ +{% endif %}{% endfor %} +{%- endif -%} + +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/common.template.html b/public/api-builder/docs-package/templates/common.template.html new file mode 100644 index 0000000000..dc10750a80 --- /dev/null +++ b/public/api-builder/docs-package/templates/common.template.html @@ -0,0 +1,9 @@ +{% extends 'layout/base.template.html' %} + +{% block body %} +

{$ doc.id $}

+

({$ doc.docType $})

+
+{$ doc.description | marked $} +
+{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/const.template.html b/public/api-builder/docs-package/templates/const.template.html new file mode 100644 index 0000000000..8b813c7d8e --- /dev/null +++ b/public/api-builder/docs-package/templates/const.template.html @@ -0,0 +1 @@ +{% extends 'var.template.html' -%} diff --git a/public/api-builder/docs-package/templates/data-module.template.js b/public/api-builder/docs-package/templates/data-module.template.js new file mode 100644 index 0000000000..498cdad8cf --- /dev/null +++ b/public/api-builder/docs-package/templates/data-module.template.js @@ -0,0 +1,3 @@ +angular.module('{$ doc.moduleName $}', []) + +.value('{$ doc.serviceName $}', {$ doc.value | json $}); \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/function.template.html b/public/api-builder/docs-package/templates/function.template.html new file mode 100644 index 0000000000..4e6d5441f6 --- /dev/null +++ b/public/api-builder/docs-package/templates/function.template.html @@ -0,0 +1,11 @@ +{% include "lib/paramList.html" -%} +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' -%} + +{% block body %} +

{$ doc.name $}{$ paramList(doc.parameters) $}

+

exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $}

+

{$ doc.description | marked $}

+ +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/guide.template.html b/public/api-builder/docs-package/templates/guide.template.html new file mode 100644 index 0000000000..3ee289dbc8 --- /dev/null +++ b/public/api-builder/docs-package/templates/guide.template.html @@ -0,0 +1,5 @@ +{% extends 'layout/base.template.html' %} + +{% block body %} +{$ doc.description | marked $} +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/interface.template.html b/public/api-builder/docs-package/templates/interface.template.html new file mode 100644 index 0000000000..3bde456ceb --- /dev/null +++ b/public/api-builder/docs-package/templates/interface.template.html @@ -0,0 +1 @@ +{% extends 'class.template.html' -%} diff --git a/public/api-builder/docs-package/templates/layout/base.template.html b/public/api-builder/docs-package/templates/layout/base.template.html new file mode 100644 index 0000000000..16a0d9dc96 --- /dev/null +++ b/public/api-builder/docs-package/templates/layout/base.template.html @@ -0,0 +1 @@ +{% block body %}{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/lib/githubLinks.html b/public/api-builder/docs-package/templates/lib/githubLinks.html new file mode 100644 index 0000000000..574656542f --- /dev/null +++ b/public/api-builder/docs-package/templates/lib/githubLinks.html @@ -0,0 +1,3 @@ +{% macro githubViewLink(doc) -%} + {$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $}) +{%- endmacro -%} diff --git a/public/api-builder/docs-package/templates/lib/paramList.html b/public/api-builder/docs-package/templates/lib/paramList.html new file mode 100644 index 0000000000..85dccb212b --- /dev/null +++ b/public/api-builder/docs-package/templates/lib/paramList.html @@ -0,0 +1,7 @@ +{% macro paramList(params) -%} + {%- if params -%}( + {%- for param in params -%} + {$ param | escape $}{% if not loop.last %}, {% endif %} + {%- endfor %}) + {%- endif %} +{%- endmacro -%} diff --git a/public/api-builder/docs-package/templates/module.template.html b/public/api-builder/docs-package/templates/module.template.html new file mode 100644 index 0000000000..c016075c48 --- /dev/null +++ b/public/api-builder/docs-package/templates/module.template.html @@ -0,0 +1,19 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' %} + +{% block body %} +

{$ doc.id $} module

+

defined in {$ githubViewLink(doc) $}

+

{$ doc.description | marked $}

+ +{% if doc.exports.length %} +

Exports

+ +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/overview-dump.template.html b/public/api-builder/docs-package/templates/overview-dump.template.html new file mode 100644 index 0000000000..3f4ee1294f --- /dev/null +++ b/public/api-builder/docs-package/templates/overview-dump.template.html @@ -0,0 +1,43 @@ +{% include "lib/paramList.html" -%} + + + + + + + + + +

Modules

+ +{% for module in doc.modules %} + +

{$ module.id $} + {%- if module.public %} (public){% endif %}

+ + {% for export in module.exports %} +

{$ export.name $}

+ + {%- if export.constructorDoc %} +

{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.params) $}

+ {% endif -%} + {%- for member in export.members %} +

{$ member.name $}{$ paramList(member.params) $}

+ {% endfor %} + + {% endfor %} + +{% endfor %} + + + diff --git a/public/api-builder/docs-package/templates/type-alias.template.html b/public/api-builder/docs-package/templates/type-alias.template.html new file mode 100644 index 0000000000..7f0ea6399f --- /dev/null +++ b/public/api-builder/docs-package/templates/type-alias.template.html @@ -0,0 +1,10 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' %} + +{% block body %} +

{$ doc.name $} type alias

+

exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $}

+

{$ doc.description | marked $}

+ +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/docs-package/templates/var.template.html b/public/api-builder/docs-package/templates/var.template.html new file mode 100644 index 0000000000..2d43873595 --- /dev/null +++ b/public/api-builder/docs-package/templates/var.template.html @@ -0,0 +1,10 @@ +{% include "lib/githubLinks.html" -%} +{% extends 'layout/base.template.html' %} + +{% block body %} +

{$ doc.name $} variable

+

exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
+defined in {$ githubViewLink(doc) $}

+

{$ doc.description | marked $}

+ +{% endblock %} \ No newline at end of file diff --git a/public/api-builder/links-package/index.js b/public/api-builder/links-package/index.js new file mode 100644 index 0000000000..a3ffc71991 --- /dev/null +++ b/public/api-builder/links-package/index.js @@ -0,0 +1,12 @@ +var Package = require('dgeni').Package; + +module.exports = new Package('links', []) + +.factory(require('./inline-tag-defs/link')) +.factory(require('dgeni-packages/ngdoc/services/getAliases')) +.factory(require('dgeni-packages/ngdoc/services/getDocFromAlias')) +.factory(require('./services/getLinkInfo')) + +.config(function(inlineTagProcessor, linkInlineTagDef) { + inlineTagProcessor.inlineTagDefinitions.push(linkInlineTagDef); +}); diff --git a/public/api-builder/links-package/inline-tag-defs/link.js b/public/api-builder/links-package/inline-tag-defs/link.js new file mode 100644 index 0000000000..89272c63ca --- /dev/null +++ b/public/api-builder/links-package/inline-tag-defs/link.js @@ -0,0 +1,33 @@ +var INLINE_LINK = /(\S+)(?:\s+([\s\S]+))?/; + +/** + * @dgService linkInlineTagDef + * @description + * Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors + * @kind function + * @param {Object} url The url to match + * @param {Function} docs error message + * @return {String} The html link information + * + * @property {boolean} relativeLinks Whether we expect the links to be relative to the originating doc + */ +module.exports = function linkInlineTagDef(getLinkInfo, createDocMessage, log) { + return { + name: 'link', + description: 'Process inline link tags (of the form {@link some/uri Some Title}), replacing them with HTML anchors', + handler: function(doc, tagName, tagDescription) { + + // Parse out the uri and title + return tagDescription.replace(INLINE_LINK, function(match, uri, title) { + + var linkInfo = getLinkInfo(uri, title, doc); + + if ( !linkInfo.valid ) { + log.warn(createDocMessage(linkInfo.error, doc)); + } + + return "" + linkInfo.title + ""; + }); + } + }; +}; \ No newline at end of file diff --git a/public/api-builder/links-package/services/getLinkInfo.js b/public/api-builder/links-package/services/getLinkInfo.js new file mode 100644 index 0000000000..787ff04338 --- /dev/null +++ b/public/api-builder/links-package/services/getLinkInfo.js @@ -0,0 +1,72 @@ +var _ = require('lodash'); +var path = require('canonical-path'); + +/** + * @dgService getLinkInfo + * @description + * Get link information to a document that matches the given url + * @kind function + * @param {String} url The url to match + * @param {String} title An optional title to return in the link information + * @return {Object} The link information + * + * @property {boolean} relativeLinks Whether we expect the links to be relative to the originating doc + */ +module.exports = function getLinkInfo(getDocFromAlias, encodeCodeBlock, log) { + + return function getLinkInfoImpl(url, title, currentDoc) { + var linkInfo = { + url: url, + type: 'url', + valid: true, + title: title || url + }; + + if ( !url ) { + throw new Error('Invalid url'); + } + + var docs = getDocFromAlias(url, currentDoc); + + if ( !getLinkInfoImpl.useFirstAmbiguousLink && docs.length > 1 ) { + + linkInfo.valid = false; + linkInfo.errorType = 'ambiguous'; + linkInfo.error = 'Ambiguous link: "' + url + '".\n' + + docs.reduce(function(msg, doc) { return msg + '\n "' + doc.id + '" ('+ doc.docType + ') : (' + doc.path + ' / ' + doc.fileInfo.relativePath + ')'; }, 'Matching docs: '); + + } else if ( docs.length >= 1 ) { + + linkInfo.url = docs[0].path; + linkInfo.title = title || encodeCodeBlock(docs[0].name, true); + linkInfo.type = 'doc'; + + if ( getLinkInfoImpl.relativeLinks && currentDoc && currentDoc.path ) { + var currentFolder = path.dirname(currentDoc.path); + var docFolder = path.dirname(linkInfo.url); + var relativeFolder = path.relative(path.join('/', currentFolder), path.join('/', docFolder)); + linkInfo.url = path.join(relativeFolder, path.basename(linkInfo.url)); + log.debug(currentDoc.path, docs[0].path, linkInfo.url); + } + + } else if ( url.indexOf('#') > 0 ) { + var pathAndHash = url.split('#'); + linkInfo = getLinkInfoImpl(pathAndHash[0], title, currentDoc); + linkInfo.url = linkInfo.url + '#' + pathAndHash[1]; + return linkInfo; + + } else if ( url.indexOf('/') === -1 && url.indexOf('#') !== 0 ) { + + linkInfo.valid = false; + linkInfo.errorType = 'missing'; + linkInfo.error = 'Invalid link (does not match any doc): "' + url + '"'; + + } else { + + linkInfo.title = title || (( url.indexOf('#') === 0 ) ? url.substring(1) : path.basename(url, '.html')); + + } + + return linkInfo; + }; +}; \ No newline at end of file diff --git a/public/api-builder/public-docs-package/index.js b/public/api-builder/public-docs-package/index.js new file mode 100644 index 0000000000..ee8823d845 --- /dev/null +++ b/public/api-builder/public-docs-package/index.js @@ -0,0 +1,24 @@ +var Package = require('dgeni').Package; +var basePackage = require('../docs-package'); + +module.exports = new Package('angular-v2-public-docs', [basePackage]) + +.config(function(readTypeScriptModules) { + readTypeScriptModules.sourceFiles = [ + 'angular2/lifecycle_hooks.ts', + 'angular2/core.ts', + 'angular2/http.ts', + 'angular2/router.ts', + 'angular2/test.ts' + ]; + readTypeScriptModules.hidePrivateMembers = true; +}) + +.config(function(getLinkInfo) { + getLinkInfo.useFirstAmbiguousLink = false; +}) + +// Configure file writing +.config(function(writeFilesProcessor) { + writeFilesProcessor.outputFolder = 'dist/public_docs'; +}); diff --git a/public/api-builder/typescript-package/index.js b/public/api-builder/typescript-package/index.js new file mode 100644 index 0000000000..def48537ae --- /dev/null +++ b/public/api-builder/typescript-package/index.js @@ -0,0 +1,70 @@ +var basePackage = require('dgeni-packages/base'); +var Package = require('dgeni').Package; +var path = require('canonical-path'); + +// Define the dgeni package for generating the docs +module.exports = new Package('typescript-parsing', [basePackage]) + +// 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('./services/convertPrivateClassesToInterfaces')) + +.factory('EXPORT_DOC_TYPES', function() { + return [ + 'class', + 'interface', + 'function', + 'var', + 'const', + 'enum', + 'type-alias' + ]; +}) + + +// Register the processors +.processor(require('./processors/readTypeScriptModules')) + + +// Configure the log service +.config(function(log) { + log.level = 'warn'; +}) + + +// Configure ids and paths +.config(function(computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES) { + + computeIdsProcessor.idTemplates.push({ + docTypes: ['member'], + idTemplate: '${classDoc.id}.${name}', + getAliases: function(doc) { return [doc.id]; } + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: ['member'], + 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 = 'partials/modules'; + + computePathsProcessor.pathTemplates.push({ + docTypes: ['module'], + pathTemplate: '/${id}', + outputPathTemplate: MODULES_DOCS_PATH + '/${id}/index.html' + }); + + computePathsProcessor.pathTemplates.push({ + docTypes: EXPORT_DOC_TYPES, + pathTemplate: '${moduleDoc.path}/${name}', + outputPathTemplate: MODULES_DOCS_PATH + '/${path}/index.html' + }); + +}); diff --git a/public/api-builder/typescript-package/mocks/mockPackage.js b/public/api-builder/typescript-package/mocks/mockPackage.js new file mode 100644 index 0000000000..834d53c959 --- /dev/null +++ b/public/api-builder/typescript-package/mocks/mockPackage.js @@ -0,0 +1,11 @@ +var Package = require('dgeni').Package; + +module.exports = function mockPackage() { + + return new Package('mockPackage', [require('../')]) + + // provide a mock log service + .factory('log', function() { return require('dgeni/lib/mocks/log')(false); }) + .factory('templateEngine', function() { return {}; }); + +}; diff --git a/public/api-builder/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts b/public/api-builder/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts new file mode 100644 index 0000000000..476d4cc44b --- /dev/null +++ b/public/api-builder/typescript-package/mocks/readTypeScriptModules/ignoreExportsMatching.ts @@ -0,0 +1,4 @@ +export var __esModule = true; +export class OKToExport {} +export function _thisIsPrivate() {} +export var thisIsOK = '!'; \ No newline at end of file diff --git a/public/api-builder/typescript-package/mocks/readTypeScriptModules/interfaces.ts b/public/api-builder/typescript-package/mocks/readTypeScriptModules/interfaces.ts new file mode 100644 index 0000000000..a2d4be4fec --- /dev/null +++ b/public/api-builder/typescript-package/mocks/readTypeScriptModules/interfaces.ts @@ -0,0 +1,5 @@ +export interface MyInterface { + optionalProperty? : string + >(param: T) : U + new (param: number) : MyInterface +} \ No newline at end of file diff --git a/public/api-builder/typescript-package/mocks/readTypeScriptModules/orderingOfMembers.ts b/public/api-builder/typescript-package/mocks/readTypeScriptModules/orderingOfMembers.ts new file mode 100644 index 0000000000..2020ec7fa4 --- /dev/null +++ b/public/api-builder/typescript-package/mocks/readTypeScriptModules/orderingOfMembers.ts @@ -0,0 +1,6 @@ +export class Test { + firstItem; + constructor() { this.doStuff(); } + otherMethod() {} + doStuff() {} +} \ No newline at end of file diff --git a/public/api-builder/typescript-package/mocks/tsParser/importedSrc.ts b/public/api-builder/typescript-package/mocks/tsParser/importedSrc.ts new file mode 100644 index 0000000000..74a115e5fe --- /dev/null +++ b/public/api-builder/typescript-package/mocks/tsParser/importedSrc.ts @@ -0,0 +1 @@ +export var x = 100; \ No newline at end of file diff --git a/public/api-builder/typescript-package/mocks/tsParser/testSrc.ts b/public/api-builder/typescript-package/mocks/tsParser/testSrc.ts new file mode 100644 index 0000000000..7b6d3f18dd --- /dev/null +++ b/public/api-builder/typescript-package/mocks/tsParser/testSrc.ts @@ -0,0 +1,34 @@ +/** + * @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/public/api-builder/typescript-package/processors/readTypeScriptModules.js b/public/api-builder/typescript-package/processors/readTypeScriptModules.js new file mode 100644 index 0000000000..5597898496 --- /dev/null +++ b/public/api-builder/typescript-package/processors/readTypeScriptModules.js @@ -0,0 +1,401 @@ +var glob = require('glob'); +var path = require('canonical-path'); +var _ = require('lodash'); +var ts = require('typescript'); + +module.exports = function readTypeScriptModules(tsParser, modules, getFileInfo, + getExportDocType, getContent, log) { + + return { + $runAfter: ['files-read'], + $runBefore: ['parsing-tags'], + + $validate: { + sourceFiles: {presence: true}, + basePath: {presence: true}, + hidePrivateMembers: {inclusion: [true, false]}, + sortClassMembers: {inclusion: [true, false]}, + ignoreExportsMatching: {} + }, + + // A collection of globs that identify those modules for which we should create docs + sourceFiles: [], + // The base path from which to load the source files + basePath: '.', + // We can ignore members of classes that are private + hidePrivateMembers: true, + // We leave class members sorted in order of declaration + sortClassMembers: false, + // We can provide a collection of strings or regexes to ignore exports whose export names match + ignoreExportsMatching: ['___esModule'], + + $process: function(docs) { + + // Convert ignoreExportsMatching to an array of regexes + var ignoreExportsMatching = convertToRegexCollection(this.ignoreExportsMatching); + + var hidePrivateMembers = this.hidePrivateMembers; + var sortClassMembers = this.sortClassMembers; + + var basePath = path.resolve(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 (anyMatches(ignoreExportsMatching, exportSymbol.name)) 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); + + exportDoc.members = []; + exportDoc.statics = []; + + // Generate docs for each of the export's members + if (resolvedExport.flags & ts.SymbolFlags.HasMembers) { + + for(var memberName in resolvedExport.members) { + // FIXME(alexeagle): why do generic type params appear in members? + if (memberName === 'T') { + continue; + } + 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); + exportDoc.members.push(memberDoc); + } else if (memberSymbol.name === '__call' && memberSymbol.flags & ts.SymbolFlags.Signature) { + docs.push(memberDoc); + exportDoc.callMember = memberDoc; + } else if (memberSymbol.name === '__new' && memberSymbol.flags & ts.SymbolFlags.Signature) { + docs.push(memberDoc); + exportDoc.newMember = memberDoc; + } + } + } + + 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 (sortClassMembers) { + exportDoc.members.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); + }); + }); + } + }; + + + 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 typeParamString = ''; + var heritageString = ''; + var typeDefinition = ''; + + exportSymbol.declarations.forEach(function(decl) { + var sourceFile = ts.getSourceFileOfNode(decl); + + if (decl.typeParameters) { + 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) { + + if (heritage.token == ts.SyntaxKind.ExtendsKeyword) { + heritageString += " extends"; + heritage.types.forEach(function(typ, idx) { + heritageString += (idx > 0 ? ',' : '') + typ.getFullText(); + }); + } + + if (heritage.token == ts.SyntaxKind.ImplementsKeyword) { + heritageString += " implements"; + heritage.types.forEach(function(typ, idx) { + heritageString += (idx > 0 ? ', ' : '') + typ.getFullText(); + }); + } + }); + } + }); + + //Make sure duplicate aliases aren't created, so "Ambiguous link" warnings are prevented + var aliasNames = [name, moduleDoc.id + '/' + name]; + if (typeParamString) { + aliasNames.push(name + typeParamString); + aliasNames.push(moduleDoc.id + '/' + name + typeParamString); + } + + var exportDoc = { + docType: getExportDocType(exportSymbol), + name: name, + id: moduleDoc.id + '/' + name, + typeParams: typeParamString, + heritage: heritageString, + decorators: getDecorators(exportSymbol), + aliases: aliasNames, + moduleDoc: moduleDoc, + content: getContent(exportSymbol), + fileInfo: getFileInfo(exportSymbol, basePath), + location: getLocation(exportSymbol) + }; + + if (exportDoc.docType === 'var' || exportDoc.docType === 'const') { + 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; + } + return exportDoc; + } + + function createMemberDoc(memberSymbol, classDoc, basePath, typeChecker) { + var memberDoc = { + docType: 'member', + classDoc: classDoc, + name: memberSymbol.name, + decorators: getDecorators(memberSymbol), + content: getContent(memberSymbol), + fileInfo: getFileInfo(memberSymbol, basePath), + location: getLocation(memberSymbol) + }; + + memberDoc.typeParameters = getTypeParameters(typeChecker, memberSymbol); + + if(memberSymbol.flags & (ts.SymbolFlags.Signature) ) { + memberDoc.parameters = getParameters(typeChecker, memberSymbol); + memberDoc.returnType = getReturnType(typeChecker, memberSymbol); + switch(memberDoc.name) { + case '__call': + memberDoc.name = ''; + break; + case '__new': + memberDoc.name = 'new'; + break; + } + } + + 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); + } + + if (memberSymbol.flags & ts.SymbolFlags.Constructor) { + memberDoc.parameters = getParameters(typeChecker, memberSymbol); + memberDoc.name = 'constructor'; + } + + if(memberSymbol.flags & ts.SymbolFlags.Value) { + memberDoc.returnType = getReturnType(typeChecker, memberSymbol); + } + + if(memberSymbol.flags & ts.SymbolFlags.Optional) { + memberDoc.optional = true; + } + + 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(); + }) + }; + }); + return decorators; + } + + function getParameters(typeChecker, symbol) { + var declaration = symbol.valueDeclaration || symbol.declarations[0]; + var sourceFile = ts.getSourceFileOfNode(declaration); + if (!declaration.parameters) { + var location = getLocation(symbol); + throw new Error('missing declaration parameters for "' + symbol.name + + '" in ' + sourceFile.fileName + + ' at line ' + location.start.line); + } + return declaration.parameters.map(function(parameter) { + var paramText = ''; + if (parameter.dotDotDotToken) { + paramText += '...'; + } + paramText += getText(sourceFile, parameter.name); + if (parameter.questionToken || parameter.initializer) { + paramText += '?'; + } + if (parameter.type) { + paramText += ':' + getType(sourceFile, parameter.type); + } else { + paramText += ': any'; + if (parameter.dotDotDotToken) { + paramText += '[]'; + } + } + return paramText.trim(); + }); + } + + function getTypeParameters(typeChecker, symbol) { + var declaration = symbol.valueDeclaration || symbol.declarations[0]; + var sourceFile = ts.getSourceFileOfNode(declaration); + if (!declaration.typeParameters) return; + var typeParams = declaration.typeParameters.map(function(type) { + return getText(sourceFile, type).trim(); + }); + return typeParams; + } + + function getReturnType(typeChecker, symbol) { + var declaration = symbol.valueDeclaration || symbol.declarations[0]; + var sourceFile = ts.getSourceFileOfNode(declaration); + if (declaration.type) { + return getType(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); + } + + + // Strip any local renamed imports from the front of types + function getType(sourceFile, type) { + var text = getText(sourceFile, type); + while (text.indexOf(".") >= 0) { + // Keep namespaced symbols in RxNext + if (text.match(/^\s*RxNext\./)) break; + // handle the case List -> List + text = text.replace(/([^.<]*)\.([^>]*)/, "$2"); + } + return text; + } + + 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 convertToRegexCollection(items) { + if (!items) return []; + + // Must be an array + if (!_.isArray(items)) { + items = [items]; + } + + // Convert string to exact matching regexes + return items.map(function(item) { + return _.isString(item) ? new RegExp('^' + item + '$') : item; + }); +} + +function anyMatches(regexes, item) { + for(var i=0; i']); + expect(exportedInterface.newMember).toBeDefined(); + expect(exportedInterface.newMember.parameters).toEqual(['param: number']); + expect(exportedInterface.newMember.returnType).toEqual('MyInterface'); + }); + }); + + + describe('ordering of members', function() { + it('should order class members in order of appearance (by default)', function() { + processor.sourceFiles = ['orderingOfMembers.ts']; + var docs = []; + processor.$process(docs); + var classDoc = _.find(docs, { docType: 'class' }); + expect(classDoc.docType).toEqual('class'); + expect(getNames(classDoc.members)).toEqual([ + 'firstItem', + 'otherMethod', + 'doStuff', + ]); + }); + + + it('should not order class members if not sortClassMembers is false', function() { + processor.sourceFiles = ['orderingOfMembers.ts']; + processor.sortClassMembers = false; + var docs = []; + processor.$process(docs); + var classDoc = _.find(docs, { docType: 'class' }); + expect(classDoc.docType).toEqual('class'); + expect(getNames(classDoc.members)).toEqual([ + 'firstItem', + 'otherMethod', + 'doStuff' + ]); + }); + }); +}); + +function getNames(collection) { + return collection.map(function(item) { return item.name; }); +} \ No newline at end of file diff --git a/public/api-builder/typescript-package/services/convertPrivateClassesToInterfaces.js b/public/api-builder/typescript-package/services/convertPrivateClassesToInterfaces.js new file mode 100644 index 0000000000..9dbe32a544 --- /dev/null +++ b/public/api-builder/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 `@private` + if (exportDoc.docType === 'class' && exportDoc.constructorDoc && exportDoc.constructorDoc.private) { + + // 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/public/api-builder/typescript-package/services/convertPrivateClassesToInterfaces.spec.js b/public/api-builder/typescript-package/services/convertPrivateClassesToInterfaces.spec.js new file mode 100644 index 0000000000..1292f30cc6 --- /dev/null +++ b/public/api-builder/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: { private: 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: { private: 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: { private: true }, + heritage: 'implements parentInterface' + } + ]; + convertPrivateClassesToInterfaces(docs, true); + expect(docs[1]).toEqual({ + docType : 'var', + name : 'privateClass', + id : 'privateClass', + returnType : 'InjectableReference' + }); + }); + +}); diff --git a/public/api-builder/typescript-package/services/modules.js b/public/api-builder/typescript-package/services/modules.js new file mode 100644 index 0000000000..7601390220 --- /dev/null +++ b/public/api-builder/typescript-package/services/modules.js @@ -0,0 +1,3 @@ +module.exports = function modules() { + return {}; +}; diff --git a/public/api-builder/typescript-package/services/tsParser/createCompilerHost.js b/public/api-builder/typescript-package/services/tsParser/createCompilerHost.js new file mode 100644 index 0000000000..5f7d672baf --- /dev/null +++ b/public/api-builder/typescript-package/services/tsParser/createCompilerHost.js @@ -0,0 +1,62 @@ +var ts = require('typescript'); +var fs = require('fs'); +var path = require('canonical-path'); + +// We need to provide our own version of CompilerHost because we want to set the +// base directory and specify what extensions to consider when trying to load a source +// file +module.exports = function createCompilerHost(log) { + + return function createCompilerHost(options, baseDir, extensions) { + + return { + getSourceFile: function(fileName, languageVersion, onError) { + 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