refact(api-builder): move into tools folder
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
var path = require('canonical-path');
|
||||
var Package = require('dgeni').Package;
|
||||
var basePackage = require('../docs-package');
|
||||
|
||||
var PROJECT_PATH = path.resolve(__dirname, "../../..");
|
||||
var PUBLIC_PATH = path.resolve(PROJECT_PATH, 'public');
|
||||
var DOCS_PATH = path.resolve(PUBLIC_PATH, 'docs');
|
||||
|
||||
module.exports = new Package('angular.io', [basePackage])
|
||||
|
||||
.factory(require('./services/renderMarkdown'))
|
||||
.processor(require('./processors/addJadeDataDocsProcessor'))
|
||||
.processor(require('./processors/filterUnwantedDecorators'))
|
||||
.processor(require('./processors/extractDirectiveClasses'))
|
||||
.processor(require('./processors/matchUpDirectiveDecorators'))
|
||||
|
||||
// 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(parseTagsProcessor) {
|
||||
parseTagsProcessor.tagDefinitions.push({ name: 'internal', transforms: function() { return true; } });
|
||||
})
|
||||
|
||||
.config(function(readTypeScriptModules, writeFilesProcessor, readFilesProcessor) {
|
||||
|
||||
readTypeScriptModules.sourceFiles = [
|
||||
'angular2/lifecycle_hooks.ts',
|
||||
'angular2/core.ts',
|
||||
'angular2/http.ts',
|
||||
'angular2/router.ts',
|
||||
'angular2/test.ts'
|
||||
];
|
||||
readTypeScriptModules.hidePrivateMembers = true;
|
||||
|
||||
readFilesProcessor.basePath = DOCS_PATH;
|
||||
writeFilesProcessor.outputFolder = 'js/latest/api';
|
||||
})
|
||||
|
||||
.config(function(getLinkInfo) {
|
||||
getLinkInfo.useFirstAmbiguousLink = false;
|
||||
})
|
||||
|
||||
|
||||
.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'],
|
||||
getPath: function computeModulePath(doc) {
|
||||
return doc.id.replace(/^angular2\//, '');
|
||||
},
|
||||
outputPathTemplate: '${path}/index.jade'
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: EXPORT_DOC_TYPES,
|
||||
pathTemplate: '${moduleDoc.path}/${name}-${docType}.html',
|
||||
outputPathTemplate:'${moduleDoc.path}/${name}-${docType}.jade',
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['jade-data'],
|
||||
pathTemplate: '${originalDoc.path}/_data',
|
||||
outputPathTemplate: '${path}.json'
|
||||
});
|
||||
|
||||
computePathsProcessor.pathTemplates.push({
|
||||
docTypes: ['app-data'],
|
||||
pathTemplate: path.resolve(PUBLIC_PATH, 'resources/js/app-data'),
|
||||
outputPathTemplate: '${path}.json'
|
||||
});
|
||||
})
|
||||
|
||||
.config(function(getLinkInfo) {
|
||||
getLinkInfo.relativeLinks = true;
|
||||
})
|
||||
|
||||
|
||||
.config(function(templateEngine, getInjectables) {
|
||||
templateEngine.filters = templateEngine.filters.concat(getInjectables([
|
||||
require('./rendering/trimBlankLines'),
|
||||
require('./rendering/toId'),
|
||||
require('./rendering/indentForMarkdown')
|
||||
]));
|
||||
})
|
||||
|
||||
.config(function(filterUnwantedDecorators, log) {
|
||||
log.level = 'info';
|
||||
filterUnwantedDecorators.decoratorsToIgnore = [
|
||||
'CONST',
|
||||
'IMPLEMENTS',
|
||||
'ABSTRACT'
|
||||
];
|
||||
})
|
||||
|
||||
@@ -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); })
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
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 = [];
|
||||
|
||||
var appDataDoc = {
|
||||
id: 'app-data',
|
||||
docType: 'app-data',
|
||||
data: {}
|
||||
};
|
||||
extraDocs.push(appDataDoc);
|
||||
|
||||
/*
|
||||
* Create Data for Modules
|
||||
*
|
||||
* Modules must be public and have content
|
||||
*/
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
if (doc.docType === 'module' && !doc.internal && 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," "),
|
||||
docType: 'module'
|
||||
}];
|
||||
|
||||
// GET DATA FOR EACH PAGE (CLASS, VARS, FUNCTIONS)
|
||||
var modulePageInfo = _(doc.exports)
|
||||
.map(function(exportDoc) {
|
||||
var dataDoc = {
|
||||
name: exportDoc.name + '-' + exportDoc.docType,
|
||||
title: exportDoc.name,
|
||||
docType: exportDoc.docType,
|
||||
exportDoc: exportDoc
|
||||
};
|
||||
if (exportDoc.symbolTypeName) dataDoc.varType = titleCase(exportDoc.symbolTypeName);
|
||||
if (exportDoc.originalModule) dataDoc.originalModule = exportDoc.originalModule;
|
||||
return dataDoc;
|
||||
})
|
||||
.sortBy('name')
|
||||
.value();
|
||||
|
||||
// ADD TO APP DATA DOC
|
||||
appDataDoc.data[doc.id] = modulePageInfo;
|
||||
|
||||
// COMBINE WITH INDEX PAGE DATA
|
||||
var allPageData = indexPageInfo.concat(modulePageInfo);
|
||||
|
||||
// PUSH JADE DATA DOC 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);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
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: 'someObj', docType: 'var', symbolTypeName: 'MyClass', originalModule: 'some/private/module' }
|
||||
],
|
||||
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', docType : 'module' },
|
||||
{ name : 'someObj-var', title : 'someObj', varType : 'MyClass', docType: 'var', originalModule: 'some/private/module' }
|
||||
] });
|
||||
});
|
||||
|
||||
it('should sort the exports into alphabetical order', function() {
|
||||
var docs = [
|
||||
{
|
||||
docType: 'module',
|
||||
id: 'someModule',
|
||||
exports: [
|
||||
{ name: 'Beta', docType: 'class'},
|
||||
{ name: 'Alpha', docType: 'class'},
|
||||
{ name: 'Gamma', docType: 'class'},
|
||||
{ name: 'Nu', docType: 'class'},
|
||||
{ name: 'Mu', docType: 'class'}
|
||||
],
|
||||
fileInfo: { baseName: 'x_y' },
|
||||
description: 'some description\nsecond line'
|
||||
}
|
||||
];
|
||||
docs = processor.$process(docs);
|
||||
|
||||
expect(docs[1].data).toEqual([
|
||||
{ name : 'index', title : 'X Y', intro : 'some description second line', docType : 'module' },
|
||||
{ name: 'Alpha-class', title: 'Alpha', docType: 'class' },
|
||||
{ name: 'Beta-class', title: 'Beta', docType: 'class' },
|
||||
{ name: 'Gamma-class', title: 'Gamma', docType: 'class' },
|
||||
{ name: 'Mu-class', title: 'Mu', docType: 'class' },
|
||||
{ name: 'Nu-class', title: 'Nu', docType: 'class' }
|
||||
]);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function extractDirectiveClassesProcessor(EXPORT_DOC_TYPES) {
|
||||
|
||||
// Add the "directive" docType into those that can be exported from a module
|
||||
EXPORT_DOC_TYPES.push('directive');
|
||||
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
decoratorTypes: ['Directive', 'Component', 'View'],
|
||||
$process: function(docs) {
|
||||
var decoratorTypes = this.decoratorTypes;
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
|
||||
_.forEach(doc.decorators, function(decorator) {
|
||||
|
||||
if (decoratorTypes.indexOf(decorator.name) !== -1) {
|
||||
doc.docType = 'directive';
|
||||
|
||||
doc[decorator.name.toLowerCase() + 'Options'] = decorator.argumentInfo[0];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
var mockPackage = require('../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('extractDirectiveClasses processor', function() {
|
||||
var dgeni, injector, processor;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('extractDirectiveClassesProcessor');
|
||||
});
|
||||
|
||||
it('should extract specified decorator arguments', function() {
|
||||
var doc = {
|
||||
id: 'angular2/angular2.ngFor',
|
||||
name: 'ngFor',
|
||||
docType: 'class',
|
||||
decorators: [
|
||||
{
|
||||
name: 'Directive',
|
||||
arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}'],
|
||||
argumentInfo: [
|
||||
{ selector: '[ng-for][ng-for-of]', properties: ['ngForOf'] }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var docs = processor.$process([doc]);
|
||||
|
||||
expect(doc).toEqual(jasmine.objectContaining({
|
||||
id: 'angular2/angular2.ngFor',
|
||||
name: 'ngFor',
|
||||
docType: 'directive',
|
||||
decorators: [
|
||||
{
|
||||
name: 'Directive',
|
||||
arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}'],
|
||||
argumentInfo: [
|
||||
{ selector: '[ng-for][ng-for-of]', properties: ['ngForOf'] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
expect(doc.directiveOptions).toEqual({
|
||||
selector: '[ng-for][ng-for-of]',
|
||||
properties: ['ngForOf']
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function filterUnwantedDecorators() {
|
||||
return {
|
||||
decoratorsToIgnore: [],
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
$process: function(docs) {
|
||||
var decoratorsToIgnore = this.decoratorsToIgnore || [];
|
||||
_.forEach(docs, function(doc) {
|
||||
if (doc.decorators) {
|
||||
doc.decorators = _.filter(doc.decorators, function(decorator) {
|
||||
return decoratorsToIgnore.indexOf(decorator.name) === -1;
|
||||
});
|
||||
}
|
||||
});
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
var mockPackage = require('../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('filterUnwantedDecorators', function() {
|
||||
var dgeni, injector, processor;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('filterUnwantedDecorators');
|
||||
});
|
||||
|
||||
|
||||
it('should remove decorators specified by name', function() {
|
||||
var docs = [
|
||||
{ id: 'doc1', decorators: [ { name: 'A' }, { name: 'B' } ] },
|
||||
{ id: 'doc2', decorators: [ { name: 'B' }, { name: 'C' } ] },
|
||||
{ id: 'doc3', decorators: [ { name: 'A' }, { name: 'C' } ] }
|
||||
];
|
||||
processor.decoratorsToIgnore = ['D', 'B'];
|
||||
docs = processor.$process(docs);
|
||||
|
||||
expect(docs).toEqual([
|
||||
{ id: 'doc1', decorators: [ { name: 'A' } ] },
|
||||
{ id: 'doc2', decorators: [ { name: 'C' } ] },
|
||||
{ id: 'doc3', decorators: [ { name: 'A' }, { name: 'C' } ] }
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore docs that have no decorators', function() {
|
||||
var docs = [
|
||||
{ id: 'doc1', decorators: [ { name: 'A' }, { name: 'B' } ] },
|
||||
{ id: 'doc2' },
|
||||
{ id: 'doc3', decorators: [ { name: 'A' }, { name: 'C' } ] }
|
||||
];
|
||||
processor.decoratorsToIgnore = ['D', 'B'];
|
||||
docs = processor.$process(docs);
|
||||
|
||||
expect(docs).toEqual([
|
||||
{ id: 'doc1', decorators: [ { name: 'A' } ] },
|
||||
{ id: 'doc2' },
|
||||
{ id: 'doc3', decorators: [ { name: 'A' }, { name: 'C' } ] }
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* @dgProcessor
|
||||
* @description
|
||||
*
|
||||
*/
|
||||
module.exports = function matchUpDirectiveDecoratorsProcessor(aliasMap) {
|
||||
|
||||
return {
|
||||
$runAfter: ['ids-computed', 'paths-computed'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
decoratorMappings: {
|
||||
'Inputs' : 'inputs',
|
||||
'Outputs' : 'outputs'
|
||||
},
|
||||
$process: function(docs) {
|
||||
var decoratorMappings = this.decoratorMappings;
|
||||
_.forEach(docs, function(doc) {
|
||||
if (doc.docType === 'directive') {
|
||||
doc.selector = doc.directiveOptions.selector;
|
||||
|
||||
for(decoratorName in decoratorMappings) {
|
||||
var propertyName = decoratorMappings[decoratorName];
|
||||
doc[propertyName] = getDecoratorValues(doc.directiveOptions[propertyName], decoratorName, doc.members);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getDecoratorValues(classDecoratorValues, memberDecoratorName, members) {
|
||||
var optionMap = {};
|
||||
var decoratorValues = {};
|
||||
|
||||
// Parse the class decorator
|
||||
_.forEach(classDecoratorValues, function(option) {
|
||||
// Options are of the form: "propName : bindingName" (bindingName is optional)
|
||||
var optionPair = option.split(':');
|
||||
var propertyName = optionPair.shift().trim();
|
||||
var bindingName = (optionPair.shift() || '').trim() || propertyName;
|
||||
|
||||
decoratorValues[propertyName] = {
|
||||
propertyName: propertyName,
|
||||
bindingName: bindingName
|
||||
};
|
||||
});
|
||||
|
||||
_.forEach(members, function(member) {
|
||||
_.forEach(member.decorators, function(decorator) {
|
||||
if (decorator.name === memberDecoratorName) {
|
||||
decoratorValues[member.name] = {
|
||||
propertyName: member.name,
|
||||
bindingName: decorator.arguments[0] || member.name
|
||||
};
|
||||
}
|
||||
});
|
||||
if (decoratorValues[member.name]) {
|
||||
decoratorValues[member.name].memberDoc = member;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(decoratorValues).length) {
|
||||
return decoratorValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
module.exports = function(encodeCodeBlock) {
|
||||
// var MIXIN_PATTERN = /\S*\+\S*\(.*/;
|
||||
return {
|
||||
name: 'indentForMarkdown',
|
||||
process: function (str, width ) {
|
||||
if(str == null || str.length === 0) {
|
||||
return '';
|
||||
}
|
||||
width = width || 4;
|
||||
|
||||
var lines = str.split('\n');
|
||||
var newLines = [];
|
||||
var sp = spaces(width);
|
||||
var spMixin = spaces(width - 2);
|
||||
var isAfterMarkdownTag = true;
|
||||
lines.forEach(function(line) {
|
||||
// indent lines that match mixin pattern by 2 less than specified width
|
||||
if (line.indexOf('{@example') >= 0) {
|
||||
if (isAfterMarkdownTag) {
|
||||
// happens if example follows example
|
||||
if (newLines.length > 0) {
|
||||
newLines.pop();
|
||||
} else {
|
||||
// wierd case - first expression in str is an @example
|
||||
// in this case the :markdown appear above the str passed in,
|
||||
// so we need to put 'something' into the markdown tag.
|
||||
newLines.push(sp + "."); // '.' is a dummy char
|
||||
}
|
||||
}
|
||||
newLines.push(spMixin + line);
|
||||
// after a mixin line we need to reenter markdown.
|
||||
newLines.push(spMixin + ':markdown');
|
||||
isAfterMarkdownTag = true;
|
||||
} else {
|
||||
if ((!isAfterMarkdownTag) || (line.trim().length > 0)) {
|
||||
newLines.push(sp + line);
|
||||
isAfterMarkdownTag = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (isAfterMarkdownTag) {
|
||||
if (newLines.length > 0) {
|
||||
// if last line is a markdown tag remove it.
|
||||
newLines.pop();
|
||||
}
|
||||
}
|
||||
// force character to be a newLine.
|
||||
if (newLines.length > 0) newLines.push('');
|
||||
var res = newLines.join('\n');
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
function spaces(n) {
|
||||
var str = '';
|
||||
for(var i=0; i<n; i++) {
|
||||
str += ' ';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = function toId() {
|
||||
return {
|
||||
name: 'toId',
|
||||
process: function(str) {
|
||||
return str.replace(/[^(a-z)(A-Z)(0-9)._-]/g, '-');
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
var factory = require('./toId');
|
||||
|
||||
describe('toId filter', function() {
|
||||
var filter;
|
||||
|
||||
beforeEach(function() {
|
||||
filter = factory();
|
||||
});
|
||||
|
||||
it('should be called "toId"', function() {
|
||||
expect(filter.name).toEqual('toId');
|
||||
});
|
||||
|
||||
it('should convert a string to make it appropriate for use as an HTML id', function() {
|
||||
expect(filter.process('This is a big string with €bad#characaters¢\nAnd even NewLines'))
|
||||
.toEqual('This-is-a-big-string-with--bad-characaters--And-even-NewLines');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
name: 'trimBlankLines',
|
||||
process: function(str) {
|
||||
var lines = str.split(/\r?\n/);
|
||||
while(lines.length && (lines[0].trim() === '')) {
|
||||
lines.shift();
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
var factory = require('./trimBlankLines');
|
||||
|
||||
describe('trimBlankLines filter', function() {
|
||||
var filter;
|
||||
|
||||
beforeEach(function() {
|
||||
filter = factory();
|
||||
});
|
||||
|
||||
it('should be called "trimBlankLines"', function() {
|
||||
expect(filter.name).toEqual('trimBlankLines');
|
||||
});
|
||||
|
||||
it('should remove all empty lines from the start of the string', function() {
|
||||
expect(filter.process('\n\n\nsome text\n\nmore text\n\n'))
|
||||
.toEqual('some text\n\nmore text\n\n');
|
||||
});
|
||||
});
|
||||
@@ -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 angularPackageJson= path.join('../angular','package.json');
|
||||
return JSON.parse(fs.readFileSync(angularPackageJson), 'UTF-8');
|
||||
}
|
||||
@@ -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 });
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
{%- for module, items in doc.data %}
|
||||
"{$ module $}" : [{% for item in items %}
|
||||
{
|
||||
"title": "{$ item.title $}",
|
||||
"path": "{$ item.exportDoc.path $}",
|
||||
"docType": "{$ item.docType $}"
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}]{% if not loop.last %},{% endif %}
|
||||
{% endfor -%}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% include "lib/paramList.html" -%}
|
||||
{% extends 'layout/base.template.html' -%}
|
||||
|
||||
{% block body %}
|
||||
include ../../_util-fns
|
||||
|
||||
h2(class="{$ doc.docType $} export")
|
||||
pre.prettyprint
|
||||
code.
|
||||
export {$ doc.docType $} {$ doc.name $}
|
||||
|
||||
p.location-badge.
|
||||
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
||||
defined in {$ githubViewLink(doc) $}
|
||||
|
||||
:markdown
|
||||
{%- if doc.notYetDocumented %}
|
||||
*Not Yet Documented*
|
||||
{% else %}
|
||||
{$ doc.description | indentForMarkdown(2) | trimBlankLines $}
|
||||
{% endif %}
|
||||
|
||||
{% block additional %}
|
||||
{% endblock %}
|
||||
|
||||
{%- if doc.decorators.length %}
|
||||
.l-main-section
|
||||
h2 Annotations
|
||||
{%- for decorator in doc.decorators %}
|
||||
.l-sub-section
|
||||
h3.annotation
|
||||
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 | toId $}
|
||||
pre.prettyprint
|
||||
code.
|
||||
{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
|
||||
:markdown
|
||||
{$ doc.constructorDoc.description | indentForMarkdown(6) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
||||
{% endif -%}
|
||||
|
||||
{%- for member in doc.members %}{% if not member.internal %}
|
||||
.l-sub-section
|
||||
h3#{$ member.name | toId $}
|
||||
pre.prettyprint
|
||||
code.
|
||||
{$ member.name $}{$ paramList(member.parameters) | indent(8, false) | trim $}{$ returnType(member.returnType) $}
|
||||
|
||||
:markdown
|
||||
{$ member.description | indentForMarkdown(6) | replace('## Example', '') | replace('# Example', '') | trimBlankLines $}
|
||||
|
||||
{% endif %}{% endfor %}
|
||||
{%- endif -%}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% include "lib/paramList.html" -%}
|
||||
{% extends 'class.template.html' -%}
|
||||
|
||||
{% block additional %}
|
||||
.l-main-section
|
||||
h2 Selectors
|
||||
.l-sub-section
|
||||
{% for selector in doc.directiveOptions.selector.split(',') %}
|
||||
h3.selector
|
||||
code {$ selector $}
|
||||
{% endfor %}
|
||||
|
||||
{% if doc.inputs %}
|
||||
.l-main-section
|
||||
h2 Inputs
|
||||
.l-sub-section{% for binding, property in doc.inputs %}
|
||||
h3.input
|
||||
code {$ property.bindingName | dashCase $}
|
||||
| bound to
|
||||
code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}
|
||||
:markdown
|
||||
{$ property.memberDoc.description | indentForMarkdown(2) | trimBlankLines $}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if doc.outputs %}
|
||||
.l-main-section
|
||||
h2 Outputs
|
||||
.l-sub-section{% for binding, property in doc.outputs %}
|
||||
h3.output
|
||||
code {$ property.bindingName | dashCase $}
|
||||
| bound to
|
||||
code {$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}
|
||||
:markdown
|
||||
{$ event.memberDoc.description | indentForMarkdown(2) | trimBlankLines $}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends 'class.template.html' -%}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% include "lib/paramList.html" -%}
|
||||
{% extends 'layout/base.template.html' -%}
|
||||
|
||||
{% block body %}
|
||||
include ../../_util-fns
|
||||
.l-main-section
|
||||
h2(class="function export")
|
||||
pre.prettyprint
|
||||
code.
|
||||
export {$ doc.name $}{$ paramList(doc.parameters) | indent(4, true) | trim $}{$ returnType(doc.returnType) $}
|
||||
|
||||
p.location-badge.
|
||||
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
||||
defined in {$ githubViewLink(doc) $}
|
||||
|
||||
:markdown
|
||||
{$ doc.description | indentForMarkdown(4) | trimBlankLines $}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
{%- 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 item.originalModule %}
|
||||
"originalModule" : "{$ item.originalModule $}",
|
||||
{%- endif %}
|
||||
"docType": "{$ item.docType $}"
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor -%}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{% block body %}{% endblock %}
|
||||
@@ -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 -%}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% extends 'layout/base.template.html' -%}
|
||||
{% block body -%}
|
||||
include ../../_util-fns
|
||||
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
|
||||
!= partial("../../../../../_includes/_hover-card", {name: page.title, url: url })
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% include "lib/paramList.html" -%}
|
||||
{% extends 'layout/base.template.html' %}
|
||||
|
||||
{% block body %}
|
||||
include ../../_util-fns
|
||||
.l-main-section
|
||||
h2(class="variable export")
|
||||
pre.prettyprint
|
||||
code.
|
||||
export {$ doc.name $}{$ returnType(doc.returnType) $}
|
||||
|
||||
p.location-badge.
|
||||
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
|
||||
defined in {$ githubViewLink(doc) $}
|
||||
|
||||
:markdown
|
||||
{%- if doc.notYetDocumented %}
|
||||
### *Not Yet Documented*
|
||||
{% else %}
|
||||
{$ doc.description | indentForMarkdown(4) | trimBlankLines $}
|
||||
{% endif -%}
|
||||
{% endblock %}
|
||||
@@ -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/extractDirectiveClasses'))
|
||||
.processor(require('./processors/generateNavigationDoc'))
|
||||
.processor(require('./processors/extractTitleFromGuides'))
|
||||
.processor(require('./processors/createOverviewDump'))
|
||||
.processor(require('./processors/checkUnbalancedBackTicks'))
|
||||
.processor(require('./processors/convertBackticksToCodeBlocks'))
|
||||
|
||||
// Configure the log service
|
||||
.config(function(log) {
|
||||
log.level = 'info';
|
||||
})
|
||||
|
||||
.config(function(parseTagsProcessor) {
|
||||
parseTagsProcessor.tagDefinitions.push({ name: 'internal', transforms: function() { return true; } });
|
||||
})
|
||||
|
||||
.config(function(renderDocsProcessor, versionInfo) {
|
||||
renderDocsProcessor.extraData.versionInfo = versionInfo;
|
||||
})
|
||||
|
||||
// Configure file reading
|
||||
.config(function(readFilesProcessor, ngdocFileReader, readTypeScriptModules) {
|
||||
readFilesProcessor.fileReaders = [ngdocFileReader];
|
||||
// set the readFilesProcessor base path to point to angular repo.
|
||||
var angular_repo_path = path.resolve(__dirname, '../../../../angular');
|
||||
// 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);
|
||||
}
|
||||
readTypeScriptModules.sourceFiles = [
|
||||
'*/*.@(js|es6|ts)',
|
||||
'*/src/**/*.@(js|es6|ts)'
|
||||
];
|
||||
readTypeScriptModules.basePath = path.resolve(angular_repo_path, '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'
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export var x = 100;
|
||||
@@ -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); });
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
@@ -0,0 +1,31 @@
|
||||
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'],
|
||||
$runAfter: ['inlineTagProcessor'],
|
||||
$runBefore: ['writeFilesProcessor'],
|
||||
$process: function(docs) {
|
||||
_.forEach(docs, function(doc) {
|
||||
if ( doc.renderedContent ) {
|
||||
var matches = doc.renderedContent.match(BACKTICK_REGEX);
|
||||
if (matches && matches.length % 2 !== 0) {
|
||||
doc.unbalancedBackTicks = true;
|
||||
log.warn(createDocMessage('checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content', doc));
|
||||
log.warn(doc.renderedContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
var mockPackage = require('../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('canonical-path');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('checkUnbalancedBackTicks', function() {
|
||||
var dgeni, injector, processor, log;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('checkUnbalancedBackTicks');
|
||||
log = injector.get('log');
|
||||
});
|
||||
|
||||
it('should warn if there are an odd number of back ticks in the rendered content', function() {
|
||||
var docs = [
|
||||
{ renderedContent:
|
||||
'```\n' +
|
||||
'code block\n' +
|
||||
'```\n' +
|
||||
'```\n' +
|
||||
'code block with missing closing back ticks\n'
|
||||
}
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(log.warn).toHaveBeenCalledWith('checkUnbalancedBackTicks processor: unbalanced backticks found in rendered content - doc');
|
||||
expect(docs[0].unbalancedBackTicks).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
|
||||
// captures below
|
||||
// 1st is entire ``` including leading whitespace
|
||||
// 2nd is the leading whitespace on the line before the ``` fence
|
||||
// 3rd is the name of the language (if any) specified after the ```
|
||||
// 4th is the contents of the ``` block up until but not including the first non whitespace char after the end ```
|
||||
// 5th is the padding of the first nonblank line following the backtick block
|
||||
// 6th is the first char on the next line.
|
||||
var BACKTICK_CAPTURE = /(( *)```(.*$)([^]*?)```\s*)^(\s*)(\S)/m;
|
||||
|
||||
var CODE_EXAMPLE = 'code-example(format="linenums" language="js").';
|
||||
|
||||
module.exports = function convertBackticksToCodeBlocks() {
|
||||
return {
|
||||
$runAfter: ['checkUnbalancedBackTicks'],
|
||||
$runBefore: ['writeFilesProcessor'],
|
||||
$process: function(docs) {
|
||||
_.forEach(docs, function(doc) {
|
||||
if (!doc.unbalancedBackTicks) {
|
||||
|
||||
// Idea here is to translate backtick ``` regions into code-example blocks.
|
||||
var captures = BACKTICK_CAPTURE.exec(doc.renderedContent);
|
||||
while (captures) {
|
||||
var entireBlock = captures[1];
|
||||
var prePad = captures[2];
|
||||
var language = captures[3];
|
||||
var blockContents = captures[4];
|
||||
var postPad = captures[5];
|
||||
var nextBlockStartChar = captures[6];
|
||||
var codeExamplePrefix = language.length ? CODE_EXAMPLE.replace('js', language) : CODE_EXAMPLE;
|
||||
// modulo op in next line insures that pad is always a multiple of 2 ( jade whitespace).
|
||||
var newPrePad = prePad.substr(2 + (prePad.length % 2)); // exdent
|
||||
var replaceVal = '\n' + newPrePad + codeExamplePrefix + escapeHtml(blockContents) + '\n';
|
||||
// if nextBlock does NOT start with a '.' then we want to restart a markdown block.
|
||||
// and that block needs to be exdented from the preceding code-example content.
|
||||
if (nextBlockStartChar != '.') {
|
||||
if (postPad.length >= 2) {
|
||||
// modulo op in next line insures that pad is always a multiple of 2 ( jade whitespace).
|
||||
postPad = postPad.substr(2 + (postPad.length % 2)); // exdent
|
||||
}
|
||||
replaceVal = replaceVal + postPad + ':markdown\n';
|
||||
}
|
||||
doc.renderedContent = doc.renderedContent.replace(entireBlock, replaceVal);
|
||||
captures = BACKTICK_CAPTURE.exec(doc.renderedContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
var mockPackage = require('../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('canonical-path');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('convertBackticksToCodeBlocks', function() {
|
||||
var dgeni, injector, processor;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('convertBackticksToCodeBlocks');
|
||||
});
|
||||
|
||||
it('should convert backtick code blocks to code-example blocks', function() {
|
||||
var docs = [{
|
||||
renderedContent:
|
||||
'preamble\n' +
|
||||
'```ts\n' +
|
||||
'export class TypeScriptClass {\n' +
|
||||
'}\n' +
|
||||
'```\n' +
|
||||
'postamble\n'
|
||||
}];
|
||||
processor.$process(docs);
|
||||
expect(docs[0].renderedContent).toEqual(
|
||||
'preamble\n' +
|
||||
'\n' +
|
||||
'code-example(format="linenums" language="ts").\n' +
|
||||
'export class TypeScriptClass {\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
':markdown\n' +
|
||||
'postamble\n'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should ignore docs that have been marked as having unbalanced backticks', function() {
|
||||
var docs = [{
|
||||
renderedContent:
|
||||
'preamble\n' +
|
||||
'```ts\n' +
|
||||
'export class TypeScriptClass {\n' +
|
||||
'}\n' +
|
||||
'postamble\n'
|
||||
}];
|
||||
processor.$process(docs);
|
||||
|
||||
expect(docs[0].renderedContent).toEqual(
|
||||
'preamble\n' +
|
||||
'```ts\n' +
|
||||
'export class TypeScriptClass {\n' +
|
||||
'}\n' +
|
||||
'postamble\n'
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = function convertPrivateClassesToInterfacesProcessor(convertPrivateClassesToInterfaces) {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
$process: function(docs) {
|
||||
convertPrivateClassesToInterfaces(docs, false);
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
var _ = require('lodash');
|
||||
var vm = require('vm');
|
||||
|
||||
module.exports = function extractDirectiveClassesProcessor() {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
decoratorTypes: ['Directive', 'Component', 'View'],
|
||||
$process: function(docs) {
|
||||
var decoratorTypes = this.decoratorTypes;
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
|
||||
_.forEach(doc.decorators, function(decorator) {
|
||||
|
||||
if (decoratorTypes.indexOf(decorator.name) !== -1) {
|
||||
|
||||
// We use this sneaky vm trick to extract the object literal
|
||||
// argument from the decorator's constructor call
|
||||
var args = decorator.arguments ?
|
||||
vm.runInNewContext('dummy = ' + decorator.arguments[0]) : {};
|
||||
|
||||
doc[decorator.name.toLowerCase() + 'Options'] = args;
|
||||
doc.docType = 'directive';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
var mockPackage = require('../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('extractDirectiveClasses processor', function() {
|
||||
var dgeni, injector, processor;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('extractDirectiveClassesProcessor');
|
||||
});
|
||||
|
||||
it('should extract specified decorator arguments', function() {
|
||||
var doc = {
|
||||
id: 'angular2/angular2.ngFor',
|
||||
name: 'ngFor',
|
||||
docType: 'class',
|
||||
decorators: [
|
||||
{
|
||||
name: 'Directive',
|
||||
arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}']
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var docs = processor.$process([doc]);
|
||||
|
||||
expect(doc).toEqual(jasmine.objectContaining({
|
||||
id: 'angular2/angular2.ngFor',
|
||||
name: 'ngFor',
|
||||
docType: 'directive',
|
||||
decorators: [
|
||||
{
|
||||
name: 'Directive',
|
||||
arguments: ['{selector: \'[ng-for][ng-for-of]\', properties: [\'ngForOf\']}']
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
expect(doc.directiveOptions).toEqual({
|
||||
selector: '[ng-for][ng-for-of]',
|
||||
properties: ['ngForOf']
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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.internal) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{% include "lib/paramList.html" -%}
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% extends 'layout/base.template.html' -%}
|
||||
|
||||
{% block body %}
|
||||
<h1 class="class export">{$ doc.name $} <span class="type">{$ doc.docType $}</span></h1>
|
||||
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
|
||||
defined in {$ githubViewLink(doc) $}
|
||||
</p>
|
||||
<p>{$ doc.description | marked $}</p>
|
||||
|
||||
{%- if doc.decorators %}
|
||||
<h2>Annotations</h2>
|
||||
{%- for decorator in doc.decorators %}
|
||||
<h3 class="annotation">@{$ decorator.name $}{$ paramList(decorator.arguments) $}</h3>
|
||||
{% endfor %}
|
||||
{% endif -%}
|
||||
|
||||
{%- if doc.constructorDoc or doc.members.length -%}
|
||||
<h2>Members</h2>
|
||||
|
||||
{%- if doc.constructorDoc %}
|
||||
<section class="member constructor">
|
||||
<h1 id="constructor" class="name">{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.params) $}</h1>
|
||||
{% marked %}
|
||||
{$ doc.constructorDoc.description $}
|
||||
{% endmarked %}
|
||||
</section>
|
||||
{% endif -%}
|
||||
|
||||
{%- for member in doc.members %}{% if not member.internal %}
|
||||
<section class="member">
|
||||
<h1 id="{$ member.name $}" class="name">
|
||||
{$ member.name $}{% if member.optional %}?{% endif %}{$ paramList(member.params) $}
|
||||
</h1>
|
||||
{% marked %}
|
||||
{$ member.description $}
|
||||
{% endmarked %}
|
||||
</section>
|
||||
|
||||
{% endif %}{% endfor %}
|
||||
{%- endif -%}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends 'layout/base.template.html' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{$ doc.id $}</h1>
|
||||
<h2>({$ doc.docType $})</h2>
|
||||
<div>
|
||||
{$ doc.description | marked $}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends 'var.template.html' -%}
|
||||
@@ -0,0 +1,3 @@
|
||||
angular.module('{$ doc.moduleName $}', [])
|
||||
|
||||
.value('{$ doc.serviceName $}', {$ doc.value | json $});
|
||||
@@ -0,0 +1,11 @@
|
||||
{% include "lib/paramList.html" -%}
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% extends 'layout/base.template.html' -%}
|
||||
|
||||
{% block body %}
|
||||
<h1 class="function export">{$ doc.name $}{$ paramList(doc.parameters) $}</h1>
|
||||
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
|
||||
defined in {$ githubViewLink(doc) $}</p>
|
||||
<p>{$ doc.description | marked $}</p>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'layout/base.template.html' %}
|
||||
|
||||
{% block body %}
|
||||
{$ doc.description | marked $}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{% extends 'class.template.html' -%}
|
||||
@@ -0,0 +1 @@
|
||||
{% block body %}{% endblock %}
|
||||
@@ -0,0 +1,3 @@
|
||||
{% macro githubViewLink(doc) -%}
|
||||
<a href="https://github.com/{$ versionInfo.gitRepoInfo.owner $}/{$ versionInfo.gitRepoInfo.repo $}/tree/{$ versionInfo.currentVersion.isSnapshot and versionInfo.currentVersion.SHA or versionInfo.currentVersion.raw $}/modules/{$ doc.fileInfo.relativePath $}#L{$ doc.location.start.line+1 $}-L{$ doc.location.end.line+1 $}">{$ doc.fileInfo.relativePath $} (line {$ doc.location.start.line+1 $})</a>
|
||||
{%- endmacro -%}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% macro paramList(params) -%}
|
||||
{%- if params -%}<span class="params">(
|
||||
{%- for param in params -%}
|
||||
<span class="param">{$ param | escape $}{% if not loop.last %}, {% endif %}</span>
|
||||
{%- endfor %})</span>
|
||||
{%- endif %}
|
||||
{%- endmacro -%}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% extends 'layout/base.template.html' %}
|
||||
|
||||
{% block body %}
|
||||
<h1 class="id">{$ doc.id $} <span class="type">module</span></h1>
|
||||
<p>defined in {$ githubViewLink(doc) $}</p>
|
||||
<p>{$ doc.description | marked $}</p>
|
||||
|
||||
{% if doc.exports.length %}
|
||||
<h2>Exports</h2>
|
||||
<ul>
|
||||
{%- for exportDoc in doc.exports %}
|
||||
{% if not exportDoc.internal -%}
|
||||
<li><a href="{$ exportDoc.path $}"><strong>{$ exportDoc.name $}</strong> {$ exportDoc.docType $}</a></li>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,43 @@
|
||||
{% include "lib/paramList.html" -%}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<style>
|
||||
h2 {
|
||||
padding-left: 20px;
|
||||
}
|
||||
h3 {
|
||||
padding-left: 50px;
|
||||
}
|
||||
h4 {
|
||||
padding-left: 60px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<h1>Modules</h1>
|
||||
|
||||
{% for module in doc.modules %}
|
||||
|
||||
<h2>{$ module.id $}
|
||||
{%- if module.public %} (public){% endif %}</h2>
|
||||
|
||||
{% for export in module.exports %}
|
||||
<h3>{$ export.name $}</h3>
|
||||
|
||||
{%- if export.constructorDoc %}
|
||||
<h4>{$ doc.constructorDoc.name $}{$ paramList(doc.constructorDoc.params) $}</h4>
|
||||
{% endif -%}
|
||||
{%- for member in export.members %}
|
||||
<h4>{$ member.name $}{$ paramList(member.params) $}</h4>
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% extends 'layout/base.template.html' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{$ doc.name $} <span class="type">type alias</span></h1>
|
||||
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
|
||||
defined in {$ githubViewLink(doc) $}</p>
|
||||
<p>{$ doc.description | marked $}</p>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% include "lib/githubLinks.html" -%}
|
||||
{% extends 'layout/base.template.html' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{$ doc.name $} <span class="type">variable</span></h1>
|
||||
<p class="module">exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }<br/>
|
||||
defined in {$ githubViewLink(doc) $}</p>
|
||||
<p>{$ doc.description | marked $}</p>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,20 @@
|
||||
var Package = require('dgeni').Package;
|
||||
|
||||
module.exports = new Package('links', [])
|
||||
|
||||
.factory(require('./inline-tag-defs/link'))
|
||||
.factory(require('./inline-tag-defs/linkDevGuide'))
|
||||
.factory(require('./inline-tag-defs/example'))
|
||||
.factory(require('./inline-tag-defs/exampleTabs'))
|
||||
.factory(require('dgeni-packages/links/services/getAliases'))
|
||||
.factory(require('dgeni-packages/links/services/getDocFromAlias'))
|
||||
.factory(require('./services/getLinkInfo'))
|
||||
.factory(require('./services/parseArgString'))
|
||||
.factory(require('./services/getApiFragmentFileName'))
|
||||
|
||||
.config(function(inlineTagProcessor, linkInlineTagDef, linkDevGuideInlineTagDef, exampleInlineTagDef, exampleTabsInlineTagDef) {
|
||||
inlineTagProcessor.inlineTagDefinitions.push(linkInlineTagDef);
|
||||
inlineTagProcessor.inlineTagDefinitions.push(linkDevGuideInlineTagDef);
|
||||
inlineTagProcessor.inlineTagDefinitions.push(exampleInlineTagDef);
|
||||
inlineTagProcessor.inlineTagDefinitions.push(exampleTabsInlineTagDef);
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
var path = require('canonical-path');
|
||||
var fs = require("fs");
|
||||
|
||||
/**
|
||||
* @dgService exampleInlineTagDef
|
||||
* @description
|
||||
* Process inline example tags (of the form {@example relativePath region -title='some title' -stylePattern='{some style pattern}' }),
|
||||
* replacing them with a jade makeExample mixin call.
|
||||
* Examples:
|
||||
* {@example core/application_spec.ts hello-app -title='Sample component' }
|
||||
* {@example core/application_spec.ts -region=hello-app -title='Sample component' }
|
||||
* @kind function
|
||||
*/
|
||||
module.exports = function exampleInlineTagDef(getLinkInfo, parseArgString, getApiFragmentFileName, createDocMessage, log) {
|
||||
return {
|
||||
name: 'example',
|
||||
description: 'Process inline example tags (of the form {@example some/uri Some Title}), replacing them with HTML anchors',
|
||||
handler: function(doc, tagName, tagDescription) {
|
||||
|
||||
var tagArgs = parseArgString(tagDescription);
|
||||
var unnamedArgs = tagArgs._;
|
||||
var relativePath = unnamedArgs[0];
|
||||
var mixinFilePath = path.join('_api', relativePath);
|
||||
var region = tagArgs.region || (unnamedArgs.length > 1 ? unnamedArgs[1] : null);
|
||||
var title = tagArgs.title || (unnamedArgs.length > 2 ? unnamedArgs[2] : null );
|
||||
// TODO: not yet implemented here
|
||||
var stylePattern = tagArgs.stylePattern;
|
||||
|
||||
// check if fragment file actually exists.
|
||||
var fragFileName = getApiFragmentFileName(relativePath, region);
|
||||
if ( !fs.existsSync(fragFileName)) {
|
||||
log.warn(createDocMessage('Invalid example (unable to locate fragment file: ' + quote(fragFileName) + ")", doc));
|
||||
}
|
||||
|
||||
var comma = ', '
|
||||
var res = [ "+makeExample(", quote(mixinFilePath), comma, region ? quote(region) : 'null', comma, title ? quote(title) : 'null', ")" ].join('');
|
||||
return res;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Examples of what @example and @exampleTabs markup looks like in the angular/angular source.
|
||||
//*
|
||||
//* {@example core/application_spec.ts hello-app -title='Sample component' }
|
||||
//*
|
||||
//* {@exampleTabs core/application_spec.ts,core/application_spec.ts "hello-app,hello-app2" -titles="Hello app1, Hello app2" }
|
||||
//*
|
||||
|
||||
|
||||
function quote(str) {
|
||||
if (str == null || str.length === 0) return str;
|
||||
str = str.replace("'","'\'");
|
||||
return "'" + str + "'";
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
var path = require('canonical-path');
|
||||
var fs = require("fs");
|
||||
|
||||
/**
|
||||
* @dgService exampleTabsInlineTagDef
|
||||
* @description
|
||||
* Process inline example tags (of the form {@example relativePath region -title='some title' -stylePattern='{some style pattern}' }),
|
||||
* replacing them with a jade makeExample mixin call.
|
||||
* Examples:
|
||||
* {@exampleTabs core/application_spec.ts,core/application_spec.ts "hello-app,hello-app2" -titles="Hello app1, Hello app2" }
|
||||
* {@exampleTabs core/application_spec.ts,core/application_spec.ts regions="hello-app,hello-app2" -titles="Hello app1, Hello app2" }
|
||||
* @kind function
|
||||
*/
|
||||
module.exports = function exampleTabsInlineTagDef(getLinkInfo, parseArgString, getApiFragmentFileName, createDocMessage, log) {
|
||||
return {
|
||||
name: 'exampleTabs',
|
||||
description: 'Process inline example tags (of the form {@example some/uri Some Title}), replacing them with HTML anchors',
|
||||
handler: function(doc, tagName, tagDescription) {
|
||||
|
||||
var tagArgs = parseArgString(tagDescription);
|
||||
var unnamedArgs = tagArgs._;
|
||||
var relativePaths = unnamedArgs[0].split(',');
|
||||
var regions = tagArgs.regions || (unnamedArgs.length > 1 ? unnamedArgs[1] : null);
|
||||
var titles = tagArgs.titles || (unnamedArgs.length > 2 ? unnamedArgs[2] : null);
|
||||
if (regions) {
|
||||
regions = regions.split(',');
|
||||
}
|
||||
|
||||
// TODO: not yet implemented here
|
||||
var stylePatterns = tagArgs.stylePattern;
|
||||
|
||||
var mixinPaths = relativePaths.map(function(relativePath, ix) {
|
||||
var fragFileName = getApiFragmentFileName(relativePath, regions && regions[ix]);
|
||||
if ( !fs.existsSync(fragFileName)) {
|
||||
log.warn(createDocMessage('Invalid example (unable to locate fragment file: ' + quote(fragFileName) + ")", doc));
|
||||
}
|
||||
return path.join('_api', relativePath);
|
||||
});
|
||||
|
||||
var comma = ', '
|
||||
var pathsArg = quote(mixinPaths.join(','));
|
||||
var regionsArg = regions ? quote(regions.join(',')) : 'null';
|
||||
var titlesArg = titles ? quote(titles) : 'null';
|
||||
var res = [ "+makeTabs(", pathsArg, comma, regionsArg, comma, titlesArg, ")" ].join('');
|
||||
return res;
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
function quote(str) {
|
||||
if (str == null || str.length === 0) return str;
|
||||
str = str.replace("'","'\'");
|
||||
return "'" + str + "'";
|
||||
}
|
||||
|
||||
@@ -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 "<a href='" + linkInfo.url + "'>" + linkInfo.title + "</a>";
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
var path = require('canonical-path');
|
||||
var fs = require("fs");
|
||||
var jsonFile = require('jsonfile');
|
||||
|
||||
var INLINE_LINK = /(\S+)(?:\s+([\s\S]+))?/;
|
||||
|
||||
/**
|
||||
* @dgService linkDevGuideInlineTagDef
|
||||
* @description
|
||||
* Process inline link tags (of the form {@linkDevGuide some/uri 'Some Title'}), replacing them with HTML anchors.
|
||||
* The uri should point to a jade page in the DevGuide without the .jade extension ( under public/docs ).
|
||||
* If the title is omitted an attempt will be made to determine the title of the jade page being pointed to. If not found
|
||||
* the the title will simply be the last part of the link.
|
||||
* Examples
|
||||
* {@linkDevGuide ts/latest/guide/gettingStarted }
|
||||
* {@linkDevGuide js/latest/guide/gettingStarted 'Javascript version of getting started' }
|
||||
* {@linkDevGuide ts/latest/guide/gettingStarted title="Typescript version of getting started" }
|
||||
* @kind function
|
||||
*/
|
||||
module.exports = function linkDevGuideInlineTagDef(parseArgString, createDocMessage, log) {
|
||||
return {
|
||||
name: 'linkDevGuide',
|
||||
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
|
||||
var tagArgs = parseArgString(tagDescription);
|
||||
var unnamedArgs = tagArgs._;
|
||||
var uri = unnamedArgs[0];
|
||||
var title = tagArgs.title || (unnamedArgs.length > 1 ? unnamedArgs[1] : null);
|
||||
|
||||
var jadePath = path.join('./public/docs', uri + '.jade');
|
||||
var key = path.basename(jadePath, '.jade');
|
||||
if ( !fs.existsSync(jadePath)) {
|
||||
log.warn(createDocMessage('Invalid DevGuide example (unable to locate jade file: "' + jadePath + '")', doc));
|
||||
} else {
|
||||
if (!title) {
|
||||
var jsonFilePath = path.join(path.dirname(jadePath), '_data.json');
|
||||
if ( fs.existsSync(jsonFilePath)) {
|
||||
var jsonObj = jsonFile.readFileSync(jsonFilePath);
|
||||
title = jsonObj[key] && jsonObj[key].title;
|
||||
}
|
||||
}
|
||||
}
|
||||
var url = path.join('/docs', uri + '.html');
|
||||
title = title || key || url;
|
||||
|
||||
return "<a href='" + url + "'>" + title + "</a>";
|
||||
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
var path = require('canonical-path');
|
||||
var FRAGMENT_DIR = "./public/docs/_fragments";
|
||||
|
||||
/**
|
||||
* @dgService getApiFragmentFileName
|
||||
* @description
|
||||
* returns the name of the api fragment file given a relative path and a region tag.
|
||||
* @kind function
|
||||
* @param {String} relativePath The relative path to the example file some part of which will be pulled in.
|
||||
* @param {String} region Name of a region within this file ( may be null).
|
||||
* @return {Object} The api fragment file name
|
||||
* @return {Object} The api fragment file name
|
||||
*/
|
||||
|
||||
module.exports = function getApiFragmentFileName() {
|
||||
|
||||
return function getApiFragmentFileName(relativePath, region) {
|
||||
var dir = path.join("_api", path.dirname(relativePath));
|
||||
var extn = path.extname(relativePath);
|
||||
var baseNameNoExtn = path.basename(relativePath, extn);
|
||||
var fileName = region ? baseNameNoExtn + "-" + region + extn : baseNameNoExtn + extn;
|
||||
var fragFileName = path.join(FRAGMENT_DIR, dir, fileName + '.md');
|
||||
return fragFileName;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @dgService parseArgString
|
||||
* @description
|
||||
* processes an arg string in 'almost' the same fashion that the command processor does
|
||||
* and returns an args object in yargs format.
|
||||
* @kind function
|
||||
* @param {String} str The arg string to process
|
||||
* @return {Object} The args parsed into a yargs format.
|
||||
*/
|
||||
|
||||
module.exports = function parseArgString() {
|
||||
|
||||
return function parseArgStringImpl(str) {
|
||||
// regex from npm string-argv
|
||||
//[^\s'"] Match if not a space ' or "
|
||||
|
||||
//+|['] or Match '
|
||||
//([^']*) Match anything that is not '
|
||||
//['] Close match if '
|
||||
|
||||
//+|["] or Match "
|
||||
//([^"]*) Match anything that is not "
|
||||
//["] Close match if "
|
||||
var rx = /[^\s'"]+|[']([^']*?)[']|["]([^"]*?)["]/gi;
|
||||
var value = str;
|
||||
var unnammedArgs = [];
|
||||
var args = {_: unnammedArgs};
|
||||
var match, key;
|
||||
do {
|
||||
//Each call to exec returns the next regex match as an array
|
||||
match = rx.exec(value);
|
||||
if (match !== null) {
|
||||
//Index 1 in the array is the captured group if it exists
|
||||
//Index 0 is the matched text, which we use if no captured group exists
|
||||
var arg = match[2] ? match[2] : (match[1] ? match[1] : match[0]);
|
||||
if (key) {
|
||||
args[key] = arg;
|
||||
key = null;
|
||||
} else {
|
||||
if (arg.substr(arg.length - 1) === '=') {
|
||||
key = arg.substr(0, arg.length - 1);
|
||||
// remove leading '-' if it exists.
|
||||
if (key.substr(0, 1) == '-') {
|
||||
key = key.substr(1);
|
||||
}
|
||||
} else {
|
||||
unnammedArgs.push(arg)
|
||||
key = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (match !== null);
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
var Package = require('dgeni').Package;
|
||||
|
||||
module.exports = new Package('target', [])
|
||||
|
||||
.factory(require('./inline-tag-defs/target'))
|
||||
|
||||
.config(function(inlineTagProcessor, targetInlineTagDef) {
|
||||
inlineTagProcessor.inlineTagDefinitions.push(targetInlineTagDef);
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* @dgService
|
||||
* @description
|
||||
* Process inline `target` block tags
|
||||
* (of the form `{@target environment1 environment2}...{@endtarget}`),
|
||||
* filtering out the blocks that do not match the containing document's
|
||||
* `targetEnvironments`.
|
||||
*/
|
||||
module.exports = function targetInlineTagDef() {
|
||||
return {
|
||||
name: 'target',
|
||||
end: 'endtarget',
|
||||
handler: function(doc, tagName, tagDescription) {
|
||||
var targets = tagDescription && tagDescription.tag.split(' ');
|
||||
if (!targets || !doc.targetEnvironments ||
|
||||
_.intersection(targets, doc.targetEnvironments).length) {
|
||||
return tagDescription.content;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
var targetFactory = require('./target');
|
||||
|
||||
describe('target inline-tag-def', function() {
|
||||
it('should filter out content that does not match the doc.targetEnvironments', function() {
|
||||
var doc, target, result;
|
||||
|
||||
doc = {
|
||||
targetEnvironments: ['js', 'es6']
|
||||
};
|
||||
|
||||
target = targetFactory();
|
||||
result = target.handler(doc, 'target', {
|
||||
tag: 'es6 ts',
|
||||
content: 'abc'
|
||||
});
|
||||
expect(result).toEqual('abc');
|
||||
|
||||
result = target.handler(doc, 'target', {
|
||||
tag: 'ts',
|
||||
content: 'xyz'
|
||||
});
|
||||
expect(result).toEqual('');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
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.classDoc.aliases.map(function(alias) { return alias + '.' + doc.name; });
|
||||
}
|
||||
});
|
||||
|
||||
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'
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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 {}; });
|
||||
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export var __esModule = true;
|
||||
export class OKToExport {}
|
||||
export function _thisIsPrivate() {}
|
||||
export var thisIsOK = '!';
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface MyInterface {
|
||||
optionalProperty? : string
|
||||
<T, U extends Findable<T>>(param: T) : U
|
||||
new (param: number) : MyInterface
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class Test {
|
||||
firstItem;
|
||||
constructor() { this.doStuff(); }
|
||||
otherMethod() {}
|
||||
doStuff() {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export var x = 10;
|
||||
@@ -0,0 +1 @@
|
||||
export { x as y} from './privateModule';
|
||||
@@ -0,0 +1 @@
|
||||
export var x = 100;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,447 @@
|
||||
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, createDocMessage, 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;
|
||||
|
||||
// If the resolved symbol contains no declarations then it is invalid
|
||||
// (probably an abstract class)
|
||||
// For the moment we are just going to ignore such exports
|
||||
// TODO: find a way of generating docs for them
|
||||
if (!resolvedExport.declarations) return;
|
||||
|
||||
var exportDoc = createExportDoc(exportSymbol.name, resolvedExport, moduleDoc, basePath, parseInfo.typeChecker);
|
||||
log.debug('>>>> EXPORT: ' + exportDoc.name + ' (' + exportDoc.docType + ') from ' + moduleDoc.id);
|
||||
|
||||
// Add this export doc to its module doc
|
||||
moduleDoc.exports.push(exportDoc);
|
||||
docs.push(exportDoc);
|
||||
|
||||
exportDoc.members = [];
|
||||
exportDoc.statics = [];
|
||||
|
||||
// Generate docs for each of the export's members
|
||||
if (resolvedExport.flags & ts.SymbolFlags.HasMembers) {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// NotYetDocumented means that no top level comments and no member level comments
|
||||
var notYetDocumented = exportDoc.content.trim().length == 0;
|
||||
exportDoc.notYetDocumented = notYetDocumented && exportDoc.members.every(function(member) {
|
||||
var content = member.content.trim();
|
||||
return content.length == 0;
|
||||
});
|
||||
|
||||
if (exportDoc.notYetDocumented) {
|
||||
log.warn(createDocMessage("Not yet documented", exportDoc));
|
||||
}
|
||||
|
||||
if (sortClassMembers) {
|
||||
exportDoc.members.sort(function(a, b) {
|
||||
if (a.name > b.name) return 1;
|
||||
if (a.name < b.name) return -1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Compute the original module name from the relative file path
|
||||
exportDoc.originalModule = exportDoc.fileInfo.relativePath
|
||||
.replace(new RegExp('\.' + exportDoc.fileInfo.extension + '$'), '');
|
||||
|
||||
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();
|
||||
}),
|
||||
argumentInfo: decorator.arguments && decorator.arguments.map(function(argument) {
|
||||
return parseArgument(argument);
|
||||
}),
|
||||
expression: decorator
|
||||
};
|
||||
});
|
||||
return decorators;
|
||||
}
|
||||
|
||||
function parseProperties(properties) {
|
||||
var result = {};
|
||||
_.forEach(properties, function(property) {
|
||||
result[property.name.text] = parseArgument(property.initializer);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseArgument(argument) {
|
||||
if (argument.text) return argument.text;
|
||||
if (argument.properties) return parseProperties(argument.properties);
|
||||
if (argument.elements) return argument.elements.map(function(element) { return element.text; });
|
||||
var sourceFile = ts.getSourceFileOfNode(argument);
|
||||
var text = getText(sourceFile, argument).trim();
|
||||
return text;
|
||||
}
|
||||
|
||||
function getParameters(typeChecker, symbol) {
|
||||
var declaration = symbol.valueDeclaration || symbol.declarations[0];
|
||||
var sourceFile = ts.getSourceFileOfNode(declaration);
|
||||
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<thing.stuff> -> List<stuff>
|
||||
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<regexes.length; ++i) {
|
||||
if ( item.match(regexes[i]) ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
var mockPackage = require('../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('canonical-path');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('readTypeScriptModules', function() {
|
||||
var dgeni, injector, processor;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('readTypeScriptModules');
|
||||
processor.basePath = path.resolve(__dirname, '../mocks/readTypeScriptModules');
|
||||
});
|
||||
|
||||
describe('exportDocs', function() {
|
||||
it('should provide the original module if the export is re-exported', function() {
|
||||
processor.sourceFiles = [ 'publicModule.ts' ];
|
||||
var docs = [];
|
||||
processor.$process(docs);
|
||||
|
||||
var exportedDoc = docs[1];
|
||||
expect(exportedDoc.originalModule).toEqual('privateModule');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ignoreExportsMatching', function() {
|
||||
it('should ignore exports that match items in the `ignoreExportsMatching` property', function() {
|
||||
processor.sourceFiles = [ 'ignoreExportsMatching.ts'];
|
||||
processor.ignoreExportsMatching = [/^_/];
|
||||
var docs = [];
|
||||
processor.$process(docs);
|
||||
|
||||
var moduleDoc = docs[0];
|
||||
expect(moduleDoc.docType).toEqual('module');
|
||||
expect(moduleDoc.exports).toEqual([
|
||||
jasmine.objectContaining({ name: 'OKToExport' }),
|
||||
jasmine.objectContaining({ name: 'thisIsOK' })
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only ignore `___esModule` exports by default', function() {
|
||||
processor.sourceFiles = [ 'ignoreExportsMatching.ts'];
|
||||
var docs = [];
|
||||
processor.$process(docs);
|
||||
|
||||
var moduleDoc = docs[0];
|
||||
expect(moduleDoc.docType).toEqual('module');
|
||||
expect(getNames(moduleDoc.exports)).toEqual([
|
||||
'OKToExport',
|
||||
'_thisIsPrivate',
|
||||
'thisIsOK'
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('interfaces', function() {
|
||||
|
||||
it('should mark optional properties', function() {
|
||||
processor.sourceFiles = [ 'interfaces.ts'];
|
||||
var docs = [];
|
||||
processor.$process(docs);
|
||||
|
||||
var moduleDoc = docs[0];
|
||||
var exportedInterface = moduleDoc.exports[0];
|
||||
var member = exportedInterface.members[0];
|
||||
expect(member.name).toEqual('optionalProperty');
|
||||
expect(member.optional).toEqual(true);
|
||||
});
|
||||
|
||||
|
||||
it('should handle "call" type interfaces', function() {
|
||||
processor.sourceFiles = [ 'interfaces.ts'];
|
||||
var docs = [];
|
||||
processor.$process(docs);
|
||||
|
||||
var moduleDoc = docs[0];
|
||||
var exportedInterface = moduleDoc.exports[0];
|
||||
|
||||
expect(exportedInterface.callMember).toBeDefined();
|
||||
expect(exportedInterface.callMember.parameters).toEqual(['param: T']);
|
||||
expect(exportedInterface.callMember.returnType).toEqual('U');
|
||||
expect(exportedInterface.callMember.typeParameters).toEqual(['T', 'U extends Findable<T>']);
|
||||
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; });
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function convertPrivateClassesToInterfaces() {
|
||||
return function(exportDocs, addInjectableReference) {
|
||||
_.forEach(exportDocs, function(exportDoc) {
|
||||
|
||||
// Search for classes with a constructor marked as `@internal`
|
||||
if (exportDoc.docType === 'class' && exportDoc.constructorDoc && exportDoc.constructorDoc.internal) {
|
||||
|
||||
// Convert this class to an interface with no constructor
|
||||
exportDoc.docType = 'interface';
|
||||
exportDoc.constructorDoc = null;
|
||||
|
||||
if (exportDoc.heritage) {
|
||||
// convert the heritage since interfaces use `extends` not `implements`
|
||||
exportDoc.heritage = exportDoc.heritage.replace('implements', 'extends');
|
||||
}
|
||||
|
||||
if (addInjectableReference) {
|
||||
// Add the `declare var SomeClass extends InjectableReference` construct
|
||||
exportDocs.push({
|
||||
docType: 'var',
|
||||
name: exportDoc.name,
|
||||
id: exportDoc.id,
|
||||
returnType: 'InjectableReference'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
var mockPackage = require('../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('readTypeScriptModules', function() {
|
||||
var dgeni, injector, convertPrivateClassesToInterfaces;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
convertPrivateClassesToInterfaces = injector.get('convertPrivateClassesToInterfaces');
|
||||
});
|
||||
|
||||
it('should convert private class docs to interface docs', function() {
|
||||
var docs = [
|
||||
{
|
||||
docType: 'class',
|
||||
name: 'privateClass',
|
||||
id: 'privateClass',
|
||||
constructorDoc: { internal: true }
|
||||
}
|
||||
];
|
||||
convertPrivateClassesToInterfaces(docs, false);
|
||||
expect(docs[0].docType).toEqual('interface');
|
||||
});
|
||||
|
||||
|
||||
it('should not touch non-private class docs', function() {
|
||||
var docs = [
|
||||
{
|
||||
docType: 'class',
|
||||
name: 'privateClass',
|
||||
id: 'privateClass',
|
||||
constructorDoc: { }
|
||||
}
|
||||
];
|
||||
convertPrivateClassesToInterfaces(docs, false);
|
||||
expect(docs[0].docType).toEqual('class');
|
||||
});
|
||||
|
||||
|
||||
it('should convert the heritage since interfaces use `extends` not `implements`', function() {
|
||||
var docs = [
|
||||
{
|
||||
docType: 'class',
|
||||
name: 'privateClass',
|
||||
id: 'privateClass',
|
||||
constructorDoc: { internal: true },
|
||||
heritage: 'implements parentInterface'
|
||||
}
|
||||
];
|
||||
convertPrivateClassesToInterfaces(docs, false);
|
||||
expect(docs[0].heritage).toEqual('extends parentInterface');
|
||||
});
|
||||
|
||||
|
||||
it('should add new injectable reference types, if specified, to the passed in collection', function() {
|
||||
var docs = [
|
||||
{
|
||||
docType: 'class',
|
||||
name: 'privateClass',
|
||||
id: 'privateClass',
|
||||
constructorDoc: { internal: true },
|
||||
heritage: 'implements parentInterface'
|
||||
}
|
||||
];
|
||||
convertPrivateClassesToInterfaces(docs, true);
|
||||
expect(docs[1]).toEqual({
|
||||
docType : 'var',
|
||||
name : 'privateClass',
|
||||
id : 'privateClass',
|
||||
returnType : 'InjectableReference'
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function modules() {
|
||||
return {};
|
||||
};
|
||||
@@ -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<extensions.length; i++) {
|
||||
|
||||
// Try reading the content from files using each of the given extensions
|
||||
try {
|
||||
resolvedPathWithExt = resolvedPath + extensions[i];
|
||||
log.silly('getSourceFile:', resolvedPathWithExt);
|
||||
text = fs.readFileSync(resolvedPathWithExt, { encoding: options.charset });
|
||||
log.debug('found source file:', fileName, resolvedPathWithExt);
|
||||
return ts.createSourceFile(baseFilePath + extensions[i], text, languageVersion);
|
||||
}
|
||||
catch(e) {
|
||||
// Try again if the file simply did not exist, otherwise report the error as a warning
|
||||
if(e.code !== 'ENOENT') {
|
||||
if (onError) onError(e.message);
|
||||
log.warn('Error reading ' + resolvedPathWithExt + ' : ' + e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getDefaultLibFileName: function(options) {
|
||||
return path.resolve(path.dirname(ts.sys.getExecutingFilePath()), ts.getDefaultLibFileName(options));
|
||||
},
|
||||
writeFile: function(fileName, data, writeByteOrderMark, onError) {
|
||||
// no-op
|
||||
},
|
||||
getCurrentDirectory: function() {
|
||||
return baseDir;
|
||||
},
|
||||
useCaseSensitiveFileNames: function() {
|
||||
return ts.sys.useCaseSensitiveFileNames;
|
||||
},
|
||||
getCanonicalFileName: function(fileName) {
|
||||
// if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form.
|
||||
// otherwise use toLowerCase as a canonical form.
|
||||
return ts.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
|
||||
},
|
||||
getNewLine: function() {
|
||||
return ts.sys.newLine;
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
var mockPackage = require('../../mocks/mockPackage');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('canonical-path');
|
||||
var ts = require('typescript');
|
||||
|
||||
describe('createCompilerHost', function() {
|
||||
var dgeni, injector, options, host, baseDir, extensions;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([mockPackage()]);
|
||||
injector = dgeni.configureInjector();
|
||||
var createCompilerHost = injector.get('createCompilerHost');
|
||||
|
||||
options = { charset: 'utf8' };
|
||||
baseDir = path.resolve(__dirname, '../../mocks/tsParser');
|
||||
extensions = ['.ts', '.js'];
|
||||
|
||||
host = createCompilerHost(options, baseDir, extensions);
|
||||
});
|
||||
|
||||
describe('getSourceFile', function() {
|
||||
it('should return a SourceFile object for a given path, with fileName relative to baseDir', function() {
|
||||
var sourceFile = host.getSourceFile('testSrc.ts');
|
||||
expect(sourceFile.fileName).toEqual('testSrc.ts');
|
||||
expect(sourceFile.pos).toEqual(0);
|
||||
expect(sourceFile.text).toEqual(jasmine.any(String));
|
||||
});
|
||||
|
||||
it('should try each of the configured extensions and update the filename to the correct extension', function() {
|
||||
var sourceFile = host.getSourceFile('testSrc.js');
|
||||
expect(sourceFile.fileName).toEqual('testSrc.ts');
|
||||
|
||||
sourceFile = host.getSourceFile('../mockPackage.ts');
|
||||
expect(sourceFile.fileName).toEqual('../mockPackage.js');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getDefaultLibFileName', function() {
|
||||
it('should return a path to the default library', function() {
|
||||
expect(host.getDefaultLibFileName(options)).toContain('typescript/bin/lib.d.ts');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('writeFile', function() {
|
||||
it('should do nothing', function() {
|
||||
host.writeFile();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getCurrentDirectory', function() {
|
||||
it('should return the baseDir', function() {
|
||||
expect(host.getCurrentDirectory()).toEqual(baseDir);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('useCaseSensitiveFileNames', function() {
|
||||
it('should return true if the OS is case sensitive', function() {
|
||||
expect(host.useCaseSensitiveFileNames()).toBe(ts.sys.useCaseSensitiveFileNames);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getCanonicalFileName', function() {
|
||||
it('should lower case the filename', function() {
|
||||
var expectedFilePath = host.useCaseSensitiveFileNames() ? 'SomeFile.ts' : 'somefile.ts';
|
||||
expect(host.getCanonicalFileName('SomeFile.ts')).toEqual(expectedFilePath);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('getNewLine', function() {
|
||||
it('should return the newline character for the OS', function() {
|
||||
expect(host.getNewLine()).toEqual(require('os').EOL);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
var ts = require('typescript');
|
||||
var LEADING_STAR = /^[^\S\r\n]*\*[^\S\n\r]?/gm;
|
||||
|
||||
module.exports = function getContent() {
|
||||
return function(symbol) {
|
||||
|
||||
var content = "";
|
||||
|
||||
if (!symbol.declarations) return content;
|
||||
|
||||
symbol.declarations.forEach(function(declaration) {
|
||||
|
||||
// If this is left side of dotted module declaration, there is no doc comment associated with this declaration
|
||||
if (declaration.kind === ts.SyntaxKind.ModuleDeclaration && declaration.body.kind === ts.SyntaxKind.ModuleDeclaration) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// If this is dotted module name, get the doc comments from the parent
|
||||
while (declaration.kind === ts.SyntaxKind.ModuleDeclaration && declaration.parent.kind === ts.SyntaxKind.ModuleDeclaration) {
|
||||
declaration = declaration.parent;
|
||||
}
|
||||
|
||||
// If this is a variable declaration then we get the doc comments from the grand parent
|
||||
if (declaration.kind === ts.SyntaxKind.VariableDeclaration) {
|
||||
declaration = declaration.parent.parent;
|
||||
}
|
||||
|
||||
// Get the source file of this declaration
|
||||
var sourceFile = ts.getSourceFileOfNode(declaration);
|
||||
var commentRanges = ts.getJsDocComments(declaration, sourceFile);
|
||||
|
||||
if (commentRanges) {
|
||||
commentRanges.forEach(function(commentRange) {
|
||||
content += sourceFile.text
|
||||
.substring(commentRange.pos+ '/**'.length, commentRange.end - '*/'.length)
|
||||
.replace(LEADING_STAR, '')
|
||||
.trim();
|
||||
if (commentRange.hasTrailingNewLine) {
|
||||
content += '\n';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
content += '\n';
|
||||
});
|
||||
|
||||
return content;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
var ts = require('typescript');
|
||||
|
||||
module.exports = function getExportDocType(log) {
|
||||
|
||||
return function(symbol) {
|
||||
if(symbol.flags & ts.SymbolFlags.Function) {
|
||||
return 'function';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.Class) {
|
||||
return 'class';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.Interface) {
|
||||
return 'interface';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.ConstEnum) {
|
||||
return 'enum';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.RegularEnum) {
|
||||
return 'enum';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.Property) {
|
||||
return 'module-property';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.TypeAlias) {
|
||||
return 'type-alias';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.FunctionScopedVariable) {
|
||||
return 'var';
|
||||
}
|
||||
if(symbol.flags & ts.SymbolFlags.BlockScopedVariable) {
|
||||
return getBlockScopedVariableDocType(symbol);
|
||||
}
|
||||
|
||||
log.warn('getExportDocType(): Unknown symbol type', {
|
||||
symbolName: symbol.name,
|
||||
symbolType: symbol.flags,
|
||||
symbolTarget: symbol.target,
|
||||
file: ts.getSourceFileOfNode(symbol.declarations[0]).fileName
|
||||
});
|
||||
return 'unknown';
|
||||
};
|
||||
|
||||
function getBlockScopedVariableDocType(symbol) {
|
||||
|
||||
var node = symbol.valueDeclaration;
|
||||
while(node) {
|
||||
if ( node.flags & 0x2000 /* const */) {
|
||||
return 'const';
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return 'let';
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
var path = require('canonical-path');
|
||||
var ts = require('typescript');
|
||||
|
||||
module.exports = function getFileInfo(log) {
|
||||
|
||||
return function (symbol, basePath) {
|
||||
var fileName = ts.getSourceFileOfNode(symbol.declarations[0]).fileName;
|
||||
|
||||
var file = path.resolve(basePath, fileName);
|
||||
var fileInfo = {
|
||||
filePath: file,
|
||||
baseName: path.basename(file, path.extname(file)),
|
||||
extension: path.extname(file).replace(/^\./, ''),
|
||||
basePath: basePath,
|
||||
relativePath: fileName,
|
||||
projectRelativePath: fileName
|
||||
};
|
||||
return fileInfo;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
var ts = require('typescript');
|
||||
var path = require('canonical-path');
|
||||
|
||||
module.exports = function tsParser(createCompilerHost, log) {
|
||||
|
||||
return {
|
||||
|
||||
// These are the extension that we should consider when trying to load a module
|
||||
// During migration from Traceur, 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.
|
||||
extensions: ['.ts', '.js'],
|
||||
|
||||
// The options for the TS compiler
|
||||
options: {
|
||||
allowNonTsExtensions: true,
|
||||
charset: 'utf8'
|
||||
},
|
||||
|
||||
parse: function(fileNames, 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, baseDir, this.extensions);
|
||||
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 & ts.SymbolFlags.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
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
@@ -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/tsParser'));
|
||||
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']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user