build(aio): move doc-gen stuff from angular.io (#14097)
This commit is contained in:
committed by
Igor Minar
parent
d1d0ce7613
commit
b7763559cd
@@ -0,0 +1,27 @@
|
||||
# Documentation Generation
|
||||
|
||||
The dgeni tool is used to generate the documentation from the source files held in this repository.
|
||||
The documentation generation is configured by a dgeni package defined in `docs/angular.io-package/index.js`.
|
||||
This package, in turn requires a number of other packages, some are defined locally in the `docs` folder,
|
||||
such as `docs/cheatsheet-package` and `docs/content-package`, etc. And some are brought in from the
|
||||
`dgeni-packages` node modules, such as `jsdoc` and `nunjucks`.
|
||||
|
||||
## Generating the docs
|
||||
|
||||
To generate the documentation simply run `gulp docs` from the command line.
|
||||
|
||||
## Testing the dgeni packages
|
||||
|
||||
The local packages have unit tests that you can execute by running `gulp docs-test` from the command line.
|
||||
|
||||
## What does it generate?
|
||||
|
||||
The output from dgeni is written to files in the `dist/docs` folder.
|
||||
|
||||
Notably this includes a partial HTML file for each "page" of the documentation, such as API pages and guides.
|
||||
It also includes JavaScript files that contain metadata about the documentation such as navigation data and
|
||||
keywords for building a search index.
|
||||
|
||||
## Viewing the docs
|
||||
|
||||
You can view the dummy demo app using a simple HTTP server hosting `dist/docs/index.html`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Package = require('dgeni').Package;
|
||||
|
||||
const jsdocPackage = require('dgeni-packages/jsdoc');
|
||||
const nunjucksPackage = require('dgeni-packages/nunjucks');
|
||||
const typescriptPackage = require('dgeni-packages/typescript');
|
||||
const gitPackage = require('dgeni-packages/git');
|
||||
const linksPackage = require('../links-package');
|
||||
const examplesPackage = require('../examples-package');
|
||||
const targetPackage = require('../target-package');
|
||||
const cheatsheetPackage = require('../cheatsheet-package');
|
||||
|
||||
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
||||
const API_SOURCE_PATH = path.resolve(PROJECT_ROOT, 'modules');
|
||||
const CONTENTS_PATH = path.resolve(PROJECT_ROOT, 'docs/content');
|
||||
const TEMPLATES_PATH = path.resolve(PROJECT_ROOT, 'docs/templates');
|
||||
|
||||
module.exports =
|
||||
new Package(
|
||||
'angular.io',
|
||||
[
|
||||
jsdocPackage, nunjucksPackage, typescriptPackage, linksPackage, examplesPackage,
|
||||
gitPackage, targetPackage, cheatsheetPackage
|
||||
])
|
||||
|
||||
// Register the processors
|
||||
.processor(require('./processors/convertPrivateClassesToInterfaces'))
|
||||
.processor(require('./processors/generateNavigationDoc'))
|
||||
.processor(require('./processors/generateKeywords'))
|
||||
.processor(require('./processors/extractTitleFromGuides'))
|
||||
.processor(require('./processors/createOverviewDump'))
|
||||
.processor(require('./processors/checkUnbalancedBackTicks'))
|
||||
.processor(require('./processors/addNotYetDocumentedProperty'))
|
||||
.processor(require('./processors/mergeDecoratorDocs'))
|
||||
.processor(require('./processors/extractDecoratedClasses'))
|
||||
.processor(require('./processors/matchUpDirectiveDecorators'))
|
||||
.processor(require('./processors/filterMemberDocs'))
|
||||
|
||||
.config(function(checkAnchorLinksProcessor, log) {
|
||||
// TODO: re-enable
|
||||
checkAnchorLinksProcessor.$enabled = false;
|
||||
})
|
||||
|
||||
// Where do we get the source files?
|
||||
.config(function(
|
||||
readTypeScriptModules, readFilesProcessor, collectExamples, generateKeywordsProcessor) {
|
||||
|
||||
// API files are typescript
|
||||
readTypeScriptModules.basePath = API_SOURCE_PATH;
|
||||
readTypeScriptModules.ignoreExportsMatching = [/^_/];
|
||||
readTypeScriptModules.hidePrivateMembers = true;
|
||||
readTypeScriptModules.sourceFiles = [
|
||||
'@angular/common/index.ts',
|
||||
'@angular/common/testing/index.ts',
|
||||
'@angular/core/index.ts',
|
||||
'@angular/core/testing/index.ts',
|
||||
'@angular/forms/index.ts',
|
||||
'@angular/http/index.ts',
|
||||
'@angular/http/testing/index.ts',
|
||||
'@angular/platform-browser/index.ts',
|
||||
'@angular/platform-browser/testing/index.ts',
|
||||
'@angular/platform-browser-dynamic/index.ts',
|
||||
'@angular/platform-browser-dynamic/testing/index.ts',
|
||||
'@angular/platform-server/index.ts',
|
||||
'@angular/platform-server/testing/index.ts',
|
||||
'@angular/platform-webworker/index.ts',
|
||||
'@angular/platform-webworker-dynamic/index.ts',
|
||||
'@angular/router/index.ts',
|
||||
'@angular/router/testing/index.ts',
|
||||
'@angular/upgrade/index.ts',
|
||||
'@angular/upgrade/static.ts',
|
||||
];
|
||||
|
||||
readFilesProcessor.basePath = PROJECT_ROOT;
|
||||
readFilesProcessor.sourceFiles = [
|
||||
{basePath: CONTENTS_PATH, include: CONTENTS_PATH + '/cheatsheet/*.md'}, {
|
||||
basePath: API_SOURCE_PATH,
|
||||
include: API_SOURCE_PATH + '/@angular/examples/**/*',
|
||||
fileReader: 'exampleFileReader'
|
||||
}
|
||||
];
|
||||
|
||||
collectExamples.exampleFolders = ['@angular/examples'];
|
||||
|
||||
generateKeywordsProcessor.ignoreWordsFile = 'tools/docs/angular.io-package/ignore.words';
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Where do we write the output files?
|
||||
.config(function(writeFilesProcessor) { writeFilesProcessor.outputFolder = 'dist/docs'; })
|
||||
|
||||
|
||||
// Target environments
|
||||
.config(function(targetEnvironments) {
|
||||
const ALLOWED_LANGUAGES = ['ts', 'js', 'dart'];
|
||||
const TARGET_LANGUAGE = 'ts';
|
||||
|
||||
ALLOWED_LANGUAGES.forEach(target => targetEnvironments.addAllowed(target));
|
||||
targetEnvironments.activate(TARGET_LANGUAGE);
|
||||
|
||||
// TODO: we may need to do something with `linkDocsInlineTagDef`
|
||||
})
|
||||
|
||||
|
||||
// Configure jsdoc-style tag parsing
|
||||
.config(function(parseTagsProcessor, getInjectables) {
|
||||
// Load up all the tag definitions in the tag-defs folder
|
||||
parseTagsProcessor.tagDefinitions =
|
||||
parseTagsProcessor.tagDefinitions.concat(getInjectables(requireFolder('./tag-defs')));
|
||||
|
||||
// We actually don't want to parse param docs in this package as we are getting the data
|
||||
// out using TS
|
||||
// TODO: rewire the param docs to the params extracted from TS
|
||||
parseTagsProcessor.tagDefinitions.forEach(function(tagDef) {
|
||||
if (tagDef.name === 'param') {
|
||||
tagDef.docProperty = 'paramData';
|
||||
tagDef.transforms = [];
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
|
||||
// Configure nunjucks rendering of docs via templates
|
||||
.config(function(
|
||||
renderDocsProcessor, versionInfo, templateFinder, templateEngine, getInjectables) {
|
||||
|
||||
// Where to find the templates for the doc rendering
|
||||
templateFinder.templateFolders = [TEMPLATES_PATH];
|
||||
// templateFinder.templateFolders.unshift(TEMPLATES_PATH);
|
||||
|
||||
// Standard patterns for matching docs to templates
|
||||
templateFinder.templatePatterns = [
|
||||
'${ doc.template }', '${ doc.id }.${ doc.docType }.template.html',
|
||||
'${ doc.id }.template.html', '${ doc.docType }.template.html',
|
||||
'${ doc.id }.${ doc.docType }.template.js', '${ doc.id }.template.js',
|
||||
'${ doc.docType }.template.js', '${ doc.id }.${ doc.docType }.template.json',
|
||||
'${ doc.id }.template.json', '${ doc.docType }.template.json', 'common.template.html'
|
||||
];
|
||||
|
||||
// Nunjucks and Angular conflict in their template bindings so change Nunjucks
|
||||
templateEngine.config.tags = {variableStart: '{$', variableEnd: '$}'};
|
||||
|
||||
templateEngine.filters =
|
||||
templateEngine.filters.concat(getInjectables(requireFolder('./rendering')));
|
||||
|
||||
// Add the version data to the renderer, for use in things like github links
|
||||
renderDocsProcessor.extraData.versionInfo = versionInfo;
|
||||
|
||||
// helpers are made available to the nunjucks templates
|
||||
renderDocsProcessor.helpers.relativePath = function(from, to) {
|
||||
return path.relative(from, to);
|
||||
};
|
||||
})
|
||||
|
||||
|
||||
|
||||
// We are going to be relaxed about ambigous links
|
||||
.config(function(getLinkInfo) {
|
||||
getLinkInfo.useFirstAmbiguousLink = false;
|
||||
// TODO: I think we don't need this for Igor's shell app
|
||||
// getLinkInfo.relativeLinks = true;
|
||||
})
|
||||
|
||||
|
||||
|
||||
.config(function(
|
||||
computeIdsProcessor, computePathsProcessor, EXPORT_DOC_TYPES, generateNavigationDoc,
|
||||
generateKeywordsProcessor) {
|
||||
|
||||
const API_SEGMENT = 'api';
|
||||
const GUIDE_SEGMENT = 'guide';
|
||||
const APP_SEGMENT = 'app';
|
||||
|
||||
generateNavigationDoc.outputFolder = APP_SEGMENT;
|
||||
generateKeywordsProcessor.outputFolder = APP_SEGMENT;
|
||||
|
||||
// Replace any path templates inherited from other packages
|
||||
// (we want full and transparent control)
|
||||
computePathsProcessor.pathTemplates = [
|
||||
{
|
||||
docTypes: ['module'],
|
||||
getPath: function computeModulePath(doc) {
|
||||
doc.moduleFolder =
|
||||
doc.id.replace(/^@angular\//, API_SEGMENT + '/').replace(/\/index$/, '');
|
||||
return doc.moduleFolder;
|
||||
},
|
||||
outputPathTemplate: '${moduleFolder}/index.html'
|
||||
},
|
||||
{
|
||||
docTypes: EXPORT_DOC_TYPES.concat(['decorator', 'directive', 'pipe']),
|
||||
pathTemplate: '${moduleDoc.moduleFolder}/${name}',
|
||||
outputPathTemplate: '${moduleDoc.moduleFolder}/${name}.html',
|
||||
},
|
||||
{
|
||||
docTypes: ['api-list-data', 'api-list-audit'],
|
||||
pathTemplate: APP_SEGMENT + '/${docType}.json',
|
||||
outputPathTemplate: '${path}'
|
||||
},
|
||||
{
|
||||
docTypes: ['cheatsheet-data'],
|
||||
pathTemplate: GUIDE_SEGMENT + '/cheatsheet.json',
|
||||
outputPathTemplate: '${path}'
|
||||
},
|
||||
{docTypes: ['example-region'], getOutputPath: function() {}}
|
||||
];
|
||||
});
|
||||
|
||||
function requireFolder(folderPath) {
|
||||
const absolutePath = path.resolve(__dirname, folderPath);
|
||||
return fs.readdirSync(absolutePath)
|
||||
.filter(p => !/[._]spec\.js$/.test(p)) // ignore spec files
|
||||
.map(p => require(path.resolve(absolutePath, p)));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const x = 100;
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 const myFn = (val: number) => val * 2;
|
||||
@@ -0,0 +1,37 @@
|
||||
module.exports = function addNotYetDocumentedProperty(EXPORT_DOC_TYPES, log, createDocMessage) {
|
||||
return {
|
||||
$runAfter: ['tags-parsed'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process: function(docs) {
|
||||
docs.forEach(function(doc) {
|
||||
|
||||
if (EXPORT_DOC_TYPES.indexOf(doc.docType) === -1) return;
|
||||
|
||||
// NotYetDocumented means that no top level comments and no member level comments
|
||||
doc.notYetDocumented = notYetDocumented(doc);
|
||||
|
||||
if (doc.constructorDoc) {
|
||||
doc.constructorDoc.notYetDocumented = notYetDocumented(doc.constructorDoc);
|
||||
doc.notYetDocumented = doc.notYetDocumented && doc.constructorDoc.notYetDocumented;
|
||||
}
|
||||
|
||||
if (doc.members) {
|
||||
doc.members.forEach(function(member) {
|
||||
member.notYetDocumented = notYetDocumented(member);
|
||||
doc.notYetDocumented = doc.notYetDocumented && member.notYetDocumented;
|
||||
});
|
||||
}
|
||||
|
||||
if (doc.notYetDocumented) {
|
||||
log.debug(createDocMessage('Not yet documented', doc));
|
||||
}
|
||||
});
|
||||
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function notYetDocumented(doc) {
|
||||
return !doc.noDescription && doc.description.trim().length == 0;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('addNotYetDocumentedProperty', function() {
|
||||
var dgeni, injector, processor, log;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('addNotYetDocumentedProperty');
|
||||
log = injector.get('log');
|
||||
});
|
||||
|
||||
it('should mark export docs with no description as "not yet documented"', function() {
|
||||
var a, b, c, d, a1, b1, c1, d1;
|
||||
var docs = [
|
||||
a = {id: 'a', docType: 'interface', description: 'some content'},
|
||||
b = {id: 'b', docType: 'class', description: 'some content'},
|
||||
c = {id: 'c', docType: 'var', description: 'some content'},
|
||||
d = {id: 'd', docType: 'function', description: 'some content'},
|
||||
a1 = {id: 'a1', docType: 'interface', description: ''},
|
||||
b1 = {id: 'b1', docType: 'class', description: ''},
|
||||
c1 = {id: 'c1', docType: 'var', description: ''},
|
||||
d1 = {id: 'd1', docType: 'function', description: ''}
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeFalsy();
|
||||
expect(c.notYetDocumented).toBeFalsy();
|
||||
expect(d.notYetDocumented).toBeFalsy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeTruthy();
|
||||
expect(b1.notYetDocumented).toBeTruthy();
|
||||
expect(c1.notYetDocumented).toBeTruthy();
|
||||
expect(d1.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should mark member docs with no description as "not yet documented"', function() {
|
||||
var a, a1, a2, b, b1, b2, c, c1, c2;
|
||||
var docs = [
|
||||
a = {
|
||||
id: 'a',
|
||||
docType: 'interface',
|
||||
description: 'some content',
|
||||
members: [a1 = {id: 'a1', description: 'some content'}, a2 = {id: 'a2', description: ''}]
|
||||
},
|
||||
b = {
|
||||
id: 'b',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
members: [b1 = {id: 'b1', description: 'some content'}, b2 = {id: 'b2', description: ''}]
|
||||
},
|
||||
c = {
|
||||
id: 'c',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
members: [c1 = {id: 'c1', description: ''}, c2 = {id: 'c2', description: ''}]
|
||||
},
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeFalsy();
|
||||
expect(c.notYetDocumented).toBeTruthy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeFalsy();
|
||||
expect(a2.notYetDocumented).toBeTruthy();
|
||||
expect(b1.notYetDocumented).toBeFalsy();
|
||||
expect(b2.notYetDocumented).toBeTruthy();
|
||||
expect(c1.notYetDocumented).toBeTruthy();
|
||||
expect(c2.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should mark constructor doc with no description as "not yet documented"', function() {
|
||||
var a, a1, b, b1;
|
||||
var docs = [
|
||||
a = {
|
||||
id: 'a',
|
||||
docType: 'interface',
|
||||
description: '',
|
||||
constructorDoc: a1 = {id: 'a1', description: 'some content'}
|
||||
},
|
||||
b = {
|
||||
id: 'b',
|
||||
docType: 'interface',
|
||||
description: '',
|
||||
constructorDoc: b1 = {id: 'b1', description: ''}
|
||||
}
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeTruthy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeFalsy();
|
||||
expect(b1.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it('should not mark documents explicity tagged as `@noDescription`', function() {
|
||||
var a, a1, a2, b, b1, b2, c, c1, c2;
|
||||
var docs = [
|
||||
a = {
|
||||
id: 'a',
|
||||
docType: 'interface',
|
||||
description: 'some content',
|
||||
members: [
|
||||
a1 = {id: 'a1', description: 'some content'},
|
||||
a2 = {id: 'a2', description: '', noDescription: true}
|
||||
]
|
||||
},
|
||||
b = {
|
||||
id: 'b',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
members: [
|
||||
b1 = {id: 'b1', description: 'some content'},
|
||||
b2 = {id: 'b2', description: '', noDescription: true}
|
||||
]
|
||||
},
|
||||
c = {
|
||||
id: 'c',
|
||||
docType: 'class',
|
||||
description: '',
|
||||
noDescription: true,
|
||||
members: [c1 = {id: 'c1', description: ''}, c2 = {id: 'c2', description: ''}]
|
||||
},
|
||||
];
|
||||
|
||||
processor.$process(docs);
|
||||
|
||||
expect(a.notYetDocumented).toBeFalsy();
|
||||
expect(b.notYetDocumented).toBeFalsy();
|
||||
expect(c.notYetDocumented).toBeFalsy();
|
||||
|
||||
expect(a1.notYetDocumented).toBeFalsy();
|
||||
expect(a2.notYetDocumented).toBeFalsy();
|
||||
expect(b1.notYetDocumented).toBeFalsy();
|
||||
expect(b2.notYetDocumented).toBeFalsy();
|
||||
expect(c1.notYetDocumented).toBeTruthy();
|
||||
expect(c2.notYetDocumented).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
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,30 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
var path = require('canonical-path');
|
||||
|
||||
describe('checkUnbalancedBackTicks', function() {
|
||||
var dgeni, injector, processor, log;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
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,11 @@
|
||||
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,29 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function extractDecoratedClassesProcessor(EXPORT_DOC_TYPES) {
|
||||
|
||||
// Add the "directive" docType into those that can be exported from a module
|
||||
EXPORT_DOC_TYPES.push('directive', 'pipe');
|
||||
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
decoratorTypes: ['Directive', 'Component', 'Pipe'],
|
||||
$process: function(docs) {
|
||||
var decoratorTypes = this.decoratorTypes;
|
||||
|
||||
_.forEach(docs, function(doc) {
|
||||
|
||||
_.forEach(doc.decorators, function(decorator) {
|
||||
|
||||
if (decoratorTypes.indexOf(decorator.name) !== -1) {
|
||||
doc.docType = decorator.name.toLowerCase();
|
||||
doc[doc.docType + 'Options'] = decorator.argumentInfo[0];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return docs;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('extractDecoratedClasses processor', function() {
|
||||
var dgeni, injector, processor;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('extractDecoratedClassesProcessor');
|
||||
});
|
||||
|
||||
it('should extract specified decorator arguments', function() {
|
||||
var doc1 = {
|
||||
id: '@angular/common/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 doc2 = {
|
||||
id: '@angular/core/DecimalPipe',
|
||||
name: 'DecimalPipe',
|
||||
docType: 'class',
|
||||
decorators:
|
||||
[{name: 'Pipe', arguments: ['{name: \'number\'}'], argumentInfo: [{name: 'number'}]}]
|
||||
};
|
||||
|
||||
processor.$process([doc1, doc2]);
|
||||
|
||||
expect(doc1).toEqual(jasmine.objectContaining({
|
||||
id: '@angular/common/ngFor',
|
||||
name: 'ngFor',
|
||||
docType: 'directive',
|
||||
directiveOptions: {selector: '[ng-for][ng-for-of]', properties: ['ngForOf']}
|
||||
}));
|
||||
|
||||
expect(doc2).toEqual(jasmine.objectContaining({
|
||||
id: '@angular/core/DecimalPipe',
|
||||
name: 'DecimalPipe',
|
||||
docType: 'pipe',
|
||||
pipeOptions: {name: 'number'}
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -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,7 @@
|
||||
module.exports = function filterMemberDocs() {
|
||||
return {
|
||||
$runAfter: ['extra-docs-added'], $runBefore: ['computing-paths'], $process: function(docs) {
|
||||
return docs.filter(function(doc) { return doc.docType !== 'member'; });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('canonical-path');
|
||||
|
||||
/**
|
||||
* @dgProcessor generateKeywordsProcessor
|
||||
* @description
|
||||
* This processor extracts all the keywords from each document and creates
|
||||
* a new document that will be rendered as a JavaScript file containing all
|
||||
* this data.
|
||||
*/
|
||||
module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
|
||||
return {
|
||||
ignoreWordsFile: undefined,
|
||||
propertiesToIgnore: [],
|
||||
docTypesToIgnore: [],
|
||||
outputFolder: '',
|
||||
$validate: {
|
||||
ignoreWordsFile: {},
|
||||
docTypesToIgnore: {},
|
||||
propertiesToIgnore: {},
|
||||
outputFolder: {presence: true}
|
||||
},
|
||||
$runAfter: ['paths-computed'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
$process: function(docs) {
|
||||
|
||||
// Keywords to ignore
|
||||
var wordsToIgnore = [];
|
||||
var propertiesToIgnore;
|
||||
var docTypesToIgnore;
|
||||
|
||||
// Keywords start with "ng:" or one of $, _ or a letter
|
||||
var KEYWORD_REGEX = /^((ng:|[$_a-z])[\w\-_]+)/;
|
||||
|
||||
// Load up the keywords to ignore, if specified in the config
|
||||
if (this.ignoreWordsFile) {
|
||||
var ignoreWordsPath = path.resolve(readFilesProcessor.basePath, this.ignoreWordsFile);
|
||||
wordsToIgnore = fs.readFileSync(ignoreWordsPath, 'utf8').toString().split(/[,\s\n\r]+/gm);
|
||||
|
||||
log.debug('Loaded ignore words from "' + ignoreWordsPath + '"');
|
||||
log.silly(wordsToIgnore);
|
||||
}
|
||||
|
||||
propertiesToIgnore = convertToMap(this.propertiesToIgnore);
|
||||
log.debug('Properties to ignore', propertiesToIgnore);
|
||||
docTypesToIgnore = convertToMap(this.docTypesToIgnore);
|
||||
log.debug('Doc types to ignore', docTypesToIgnore);
|
||||
|
||||
var ignoreWordsMap = convertToMap(wordsToIgnore);
|
||||
|
||||
// If the title contains a name starting with ng, e.g. "ngController", then add the module
|
||||
// name
|
||||
// without the ng to the title text, e.g. "controller".
|
||||
function extractTitleWords(title) {
|
||||
var match = /ng([A-Z]\w*)/.exec(title);
|
||||
if (match) {
|
||||
title = title + ' ' + match[1].toLowerCase();
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
function extractWords(text, words, keywordMap) {
|
||||
var tokens = text.toLowerCase().split(/[.\s,`'"#]+/mg);
|
||||
tokens.forEach(function(token) {
|
||||
var match = token.match(KEYWORD_REGEX);
|
||||
if (match) {
|
||||
var key = match[1];
|
||||
if (!keywordMap[key]) {
|
||||
keywordMap[key] = true;
|
||||
words.push(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// We are only interested in docs that live in the right area
|
||||
const filteredDocs = docs.filter(function(doc) { return !docTypesToIgnore[doc.docType]; });
|
||||
|
||||
filteredDocs.forEach(function(doc) {
|
||||
|
||||
|
||||
var words = [];
|
||||
var keywordMap = Object.assign({}, ignoreWordsMap);
|
||||
var members = [];
|
||||
var membersMap = {};
|
||||
|
||||
// Search each top level property of the document for search terms
|
||||
Object.keys(doc).forEach(function(key) {
|
||||
const value = doc[key];
|
||||
|
||||
if (isString(value) && !propertiesToIgnore[key]) {
|
||||
extractWords(value, words, keywordMap);
|
||||
}
|
||||
|
||||
if (key === 'methods' || key === 'properties' || key === 'events') {
|
||||
value.forEach(function(member) { extractWords(member.name, members, membersMap); });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
doc.searchTerms = {
|
||||
titleWords: extractTitleWords(doc.name),
|
||||
keywords: words.sort().join(' '),
|
||||
members: members.sort().join(' ')
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
var searchData =
|
||||
filteredDocs.filter(function(page) { return page.searchTerms; }).map(function(page) {
|
||||
return Object.assign(
|
||||
{path: page.path, title: page.name, type: page.docType}, page.searchTerms);
|
||||
});
|
||||
|
||||
docs.push({
|
||||
docType: 'json-doc',
|
||||
id: 'search-data-json',
|
||||
template: 'json-doc.template.json',
|
||||
path: this.outputFolder + '/search-data.json',
|
||||
outputPath: this.outputFolder + '/search-data.json',
|
||||
data: searchData
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function isString(value) {
|
||||
return typeof value == 'string';
|
||||
}
|
||||
|
||||
function convertToMap(collection) {
|
||||
const obj = {};
|
||||
collection.forEach(key => { obj[key] = true; });
|
||||
return obj;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
module.exports = function generateNavigationDoc() {
|
||||
|
||||
return {
|
||||
$runAfter: ['extra-docs-added'],
|
||||
$runBefore: ['rendering-docs'],
|
||||
outputFolder: '',
|
||||
$validate: {outputFolder: {presence: true}},
|
||||
$process: function(docs) {
|
||||
var modulesDoc = {
|
||||
docType: 'data-module',
|
||||
value: {api: {sections: []}, guide: {pages: []}},
|
||||
path: this.outputFolder + '/navigation',
|
||||
outputPath: this.outputFolder + '/navigation.ts',
|
||||
serviceName: 'NAVIGATION'
|
||||
};
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
if (doc.docType === 'module') {
|
||||
var moduleNavItem =
|
||||
{path: doc.path, partial: doc.outputPath, name: doc.id, type: 'module', pages: []};
|
||||
|
||||
modulesDoc.value.api.sections.push(moduleNavItem);
|
||||
|
||||
doc.exports.forEach(function(exportDoc) {
|
||||
if (!exportDoc.internal) {
|
||||
var exportNavItem = {
|
||||
path: exportDoc.path,
|
||||
partial: exportDoc.outputPath,
|
||||
name: exportDoc.name,
|
||||
type: exportDoc.docType
|
||||
};
|
||||
moduleNavItem.pages.push(exportNavItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
if (doc.docType === 'guide') {
|
||||
console.log('guide', doc.name);
|
||||
var guideDoc = {path: doc.path, partial: doc.outputPath, name: doc.name, type: 'guide'};
|
||||
modulesDoc.value.guide.pages.push(guideDoc);
|
||||
}
|
||||
});
|
||||
|
||||
docs.push(modulesDoc);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
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,97 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function mergeDecoratorDocs() {
|
||||
return {
|
||||
$runAfter: ['processing-docs'],
|
||||
$runBefore: ['docs-processed'],
|
||||
docsToMergeInfo: [
|
||||
{nameTemplate: _.template('${name}Decorator'), decoratorProperty: 'decoratorInterfaceDoc'}, {
|
||||
nameTemplate: _.template('${name}Metadata'),
|
||||
decoratorProperty: 'metadataDoc',
|
||||
useFields: ['howToUse', 'whatItDoes']
|
||||
},
|
||||
{nameTemplate: _.template('${name}MetadataType'), decoratorProperty: 'metadataInterfaceDoc'},
|
||||
{
|
||||
nameTemplate: _.template('${name}MetadataFactory'),
|
||||
decoratorProperty: 'metadataFactoryDoc'
|
||||
}
|
||||
],
|
||||
$process: function(docs) {
|
||||
|
||||
var docsToMergeInfo = this.docsToMergeInfo;
|
||||
var docsToMerge = Object.create(null);
|
||||
|
||||
docs.forEach(function(doc) {
|
||||
|
||||
// find all the decorators, signified by a call to `makeDecorator(metadata)`
|
||||
var makeDecorator = getMakeDecoratorCall(doc);
|
||||
if (makeDecorator) {
|
||||
doc.docType = 'decorator';
|
||||
// get the type of the decorator metadata
|
||||
doc.decoratorType = makeDecorator.arguments[0].text;
|
||||
// clear the symbol type named (e.g. ComponentMetadataFactory) since it is not needed
|
||||
doc.symbolTypeName = undefined;
|
||||
|
||||
// keep track of the docs that need to be merged into this decorator doc
|
||||
docsToMergeInfo.forEach(function(info) {
|
||||
docsToMerge[info.nameTemplate({name: doc.name})] = {
|
||||
decoratorDoc: doc,
|
||||
property: info.decoratorProperty
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// merge the metadata docs into the decorator docs
|
||||
docs = docs.filter(function(doc) {
|
||||
if (docsToMerge[doc.name]) {
|
||||
var decoratorDoc = docsToMerge[doc.name].decoratorDoc;
|
||||
var property = docsToMerge[doc.name].property;
|
||||
var useFields = docsToMerge[doc.name].useFields;
|
||||
|
||||
// attach this document to its decorator
|
||||
decoratorDoc[property] = doc;
|
||||
|
||||
// Copy over fields from the merged doc if specified
|
||||
if (useFields) {
|
||||
useFields.forEach(function(field) { decoratorDoc[field] = doc[field]; });
|
||||
}
|
||||
|
||||
// remove doc from its module doc's exports
|
||||
doc.moduleDoc.exports =
|
||||
doc.moduleDoc.exports.filter(function(exportDoc) { return exportDoc !== doc; });
|
||||
|
||||
|
||||
// remove from the overall list of docs to be rendered
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function getMakeDecoratorCall(doc, type) {
|
||||
var makeDecoratorFnName = 'make' + (type || '') + 'Decorator';
|
||||
|
||||
var initializer = doc.exportSymbol && doc.exportSymbol.valueDeclaration &&
|
||||
doc.exportSymbol.valueDeclaration.initializer;
|
||||
|
||||
if (initializer) {
|
||||
// There appear to be two forms of initializer:
|
||||
// export var Injectable: InjectableFactory =
|
||||
// <InjectableFactory>makeDecorator(InjectableMetadata);
|
||||
// and
|
||||
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
|
||||
// makeDecorator(RouteConfigAnnotation);
|
||||
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
|
||||
// extra level of expression
|
||||
// to hold the new type of the expression.
|
||||
if (initializer.expression && initializer.expression.expression) {
|
||||
initializer = initializer.expression;
|
||||
}
|
||||
if (initializer.expression && initializer.expression.text === makeDecoratorFnName) {
|
||||
return initializer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
var testPackage = require('../../helpers/test-package');
|
||||
var Dgeni = require('dgeni');
|
||||
|
||||
describe('mergeDecoratorDocs processor', function() {
|
||||
var dgeni, injector, processor, decoratorDoc, otherDoc;
|
||||
|
||||
beforeEach(function() {
|
||||
dgeni = new Dgeni([testPackage('angular.io-package')]);
|
||||
injector = dgeni.configureInjector();
|
||||
processor = injector.get('mergeDecoratorDocs');
|
||||
|
||||
decoratorDoc = {
|
||||
name: 'X',
|
||||
docType: 'var',
|
||||
exportSymbol: {
|
||||
valueDeclaration: {
|
||||
initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'XMetadata'}]}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
decoratorDocWithTypeAssertion = {
|
||||
name: 'Y',
|
||||
docType: 'var',
|
||||
exportSymbol: {
|
||||
valueDeclaration: {
|
||||
initializer: {
|
||||
expression: {
|
||||
type: {},
|
||||
expression: {text: 'makeDecorator'},
|
||||
arguments: [{text: 'YMetadata'}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
otherDoc = {
|
||||
name: 'Y',
|
||||
docType: 'var',
|
||||
exportSymbol: {
|
||||
valueDeclaration:
|
||||
{initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
it('should change the docType of only the docs that are initialied by a call to makeDecorator',
|
||||
function() {
|
||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
expect(decoratorDoc.docType).toEqual('decorator');
|
||||
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
|
||||
expect(otherDoc.docType).toEqual('var');
|
||||
});
|
||||
|
||||
it('should extract the "type" of the decorator meta data', function() {
|
||||
processor.$process([decoratorDoc, decoratorDocWithTypeAssertion, otherDoc]);
|
||||
expect(decoratorDoc.decoratorType).toEqual('XMetadata');
|
||||
expect(decoratorDocWithTypeAssertion.decoratorType).toEqual('YMetadata');
|
||||
});
|
||||
});
|
||||
@@ -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 :marked 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 + ':marked');
|
||||
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,6 @@
|
||||
module.exports = function toId() {
|
||||
return {
|
||||
name: 'toId',
|
||||
process: function(str) { return str.replace(/[^(a-z)(A-Z)(0-9)._-]/g, '-'); }
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
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,15 @@
|
||||
module.exports = function() {
|
||||
return {
|
||||
name: 'trimBlankLines',
|
||||
process: function(str) {
|
||||
var lines = str.split(/\r?\n/);
|
||||
while (lines.length && (lines[0].trim() === '')) {
|
||||
lines.shift();
|
||||
}
|
||||
while (lines.length && (lines[lines.length - 1].trim() === '')) {
|
||||
lines.pop();
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
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 empty lines from the start and end of the string', function() {
|
||||
expect(filter.process('\n \n\nsome text\n \nmore text\n \n'))
|
||||
.toEqual('some text\n \nmore text');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
// A ts2dart compiler annotation that can be ignored in API docs.
|
||||
module.exports = function() {
|
||||
return {name: 'Annotation', ignore: true};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'deprecated'};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'docsNotRequired'};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'experimental'};
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
return {name: 'howToUse'};
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user