build(aio): move doc-gen stuff from angular.io (#14097)

This commit is contained in:
Peter Bacon Darwin 2017-01-26 14:03:53 +00:00 committed by Igor Minar
parent d1d0ce7613
commit b7763559cd
135 changed files with 5031 additions and 1 deletions

View File

@ -0,0 +1,44 @@
/*
Copyright 2016 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 http://angular.io/license
*/
import {Component, OnInit, NgZone } from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/switchMap';
import {QueryResults, SearchWorkerClient} from './search-worker-client';
@Component({
selector: 'my-app',
template: `
<h1>Angular Docs Search</h1>
<div class="search-bar"><input [formControl]="searchInput"></div>
<div class="search-results">
<div *ngIf="!(indexReady | async)">Waiting...</div>
<ul>
<li *ngFor="let result of (searchResult$ | async)">
<a href="{{result.path}}">{{ result.title }} ({{result.type}})</a>
</li>
</ul>
</div>
`
})
export class AppComponent implements OnInit {
searchResult$: Observable<QueryResults>;
indexReady: Promise<boolean>;
searchInput: FormControl;
constructor(private zone: NgZone) {}
ngOnInit() {
const searchWorker = new SearchWorkerClient('app/search-worker.js', this.zone);
this.indexReady = searchWorker.ready;
this.searchInput = new FormControl();
this.searchResult$ = this.searchInput.valueChanges
.switchMap((searchText: string) => searchWorker.search(searchText));
}
}

21
docs/src/app/main.ts Normal file
View File

@ -0,0 +1,21 @@
/*
Copyright 2016 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 http://angular.io/license
*/
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ReactiveFormsModule} from '@angular/forms';
import {AppComponent} from 'app/app.component';
@NgModule({
imports: [ BrowserModule, ReactiveFormsModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
class AppModule {
}
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,97 @@
/*
Copyright 2016 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 http://angular.io/license
*/
import {NgZone} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subscriber} from 'rxjs/Subscriber';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/switchMap';
export interface QueryResults {}
export interface ResultsReadyMessage {
type: 'query-results';
id: number;
query: string;
results: QueryResults;
}
export class SearchWorkerClient {
ready: Promise<boolean>;
worker: Worker;
private _queryId = 0;
constructor(url: string, private zone: NgZone) {
this.worker = new Worker(url);
this.ready = this._waitForIndex(this.worker);
}
search(query: string) {
return Observable.fromPromise(this.ready)
.switchMap(() => this._createQuery(query));
}
private _waitForIndex(worker: Worker) {
return new Promise((resolve, reject) => {
worker.onmessage = (e) => {
if(e.data.type === 'index-ready') {
resolve(true);
cleanup();
}
};
worker.onerror = (e) => {
reject(e);
cleanup();
};
});
function cleanup() {
worker.onmessage = null;
worker.onerror = null;
}
}
private _createQuery(query: string) {
return new Observable<QueryResults>((subscriber: Subscriber<QueryResults>) => {
// get a new identifier for this query that we can match to results
const id = this._queryId++;
const handleMessage = (message: MessageEvent) => {
const {type, id: queryId, results} = message.data as ResultsReadyMessage;
if (type === 'query-results' && id === queryId) {
this.zone.run(() => {
subscriber.next(results);
subscriber.complete();
});
}
};
const handleError = (error: ErrorEvent) => {
this.zone.run(() => {
subscriber.error(error);
});
};
// Wire up the event listeners for this query
this.worker.addEventListener('message', handleMessage);
this.worker.addEventListener('error', handleError);
// Post the query to the web worker
this.worker.postMessage({query, id});
// At completion/error unwire the event listeners
return () => {
this.worker.removeEventListener('message', handleMessage);
this.worker.removeEventListener('error', handleError);
};
});
}
}

View File

@ -0,0 +1,63 @@
'use strict';
/* eslint-env worker */
/* global importScripts, lunr */
importScripts('https://unpkg.com/lunr@0.7.2');
var index = createIndex();
var pages = {};
makeRequest('search-data.json', loadIndex);
self.onmessage = handleMessage;
// Create the lunr index - the docs should be an array of objects, each object containing
// the path and search terms for a page
function createIndex() {
return lunr(/** @this */function() {
this.ref('path');
this.field('titleWords', {boost: 50});
this.field('members', {boost: 40});
this.field('keywords', {boost: 20});
});
}
// Use XHR to make a request to the server
function makeRequest(url, callback) {
var searchDataRequest = new XMLHttpRequest();
searchDataRequest.onload = function() {
callback(JSON.parse(this.responseText));
};
searchDataRequest.open('GET', url);
searchDataRequest.send();
}
// Create the search index from the searchInfo which contains the information about each page to be indexed
function loadIndex(searchInfo) {
// Store the pages data to be used in mapping query results back to pages
// Add search terms from each page to the search index
searchInfo.forEach(function(page) {
index.add(page);
pages[page.path] = page;
});
self.postMessage({type: 'index-ready'});
}
// The worker receives a message everytime the web app wants to query the index
function handleMessage(message) {
var id = message.data.id;
var query = message.data.query;
var results = queryIndex(query);
self.postMessage({type: 'query-results', id: id, query: query, results: results});
}
// Query the index and return the processed results
function queryIndex(query) {
// Only return the array of paths to pages
return index.search(query).map(function(hit) { return pages[hit.ref]; });
}

View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"baseUrl": ".",
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../dist/tools/",
"noImplicitAny": true,
"noFallthroughCasesInSwitch": true,
"paths": {
},
"rootDir": ".",
"sourceMap": true,
"inlineSources": true,
"lib": ["es6", "dom"],
"target": "es5",
"skipLibCheck": true,
"typeRoots": [
"../../../node_modules"
]
},
"exclude": [
"node_modules",
"typings-test",
"public_api_guard",
"docs"
]
}

28
docs/src/index.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello Angular</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {color:#369;font-family: Arial,Helvetica,sans-serif;}
</style>
<!-- Polyfills for older browsers -->
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.7.2?main=browser"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.8"></script>
<script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
<script src="systemjs.config.web.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>Loading AppComponent content here ...</my-app>
</body>
</html>

View File

@ -0,0 +1,64 @@
/*
Copyright 2016 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 http://angular.io/license
*/
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
// Copy of compiler options in standard tsconfig.json
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'app',
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
'typescript': 'npm:typescript@2.0.3/lib/typescript.js',
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
}
}
});

View File

@ -0,0 +1,15 @@
[{% for module, items in doc.data %}
{% for item in items %}
{
"title": "{$ item.title $}",
"path": "{$ item.exportDoc.path $}",
"docType": "{$ item.docType $}",
"stability": "{$ item.stability $}",
"secure": "{$ item.security $}",
"howToUse": "{$ item.howToUse | replace('"','\\"') $}",
"whatItDoes": {% if item.whatItDoes %}"Exists"{% else %}"Not Done"{% endif %},
"barrel" : "{$ module | replace("/index", "") $}"
}{% if not loop.last %},{% endif %}
{% endfor %}
{% endfor %}
]

View File

@ -0,0 +1,14 @@
{
{%- for module, items in doc.data %}
"{$ module | replace("/index", "") $}" : [{% for item in items %}
{
"title": "{$ item.title $}",
"path": "{$ item.exportDoc.path $}",
"docType": "{$ item.docType $}",
"stability": "{$ item.stability $}",
"secure": "{$ item.security $}",
"barrel" : "{$ module | replace("/index", "") $}"
}{% if not loop.last %},{% endif %}
{% endfor %}]{% if not loop.last %},{% endif %}
{% endfor -%}
}

View File

@ -0,0 +1,5 @@
{
"currentEnvironment": {$ doc.currentEnvironment | json | trim $},
"version": {$ doc.version.currentVersion | json | indent(2) | trim $},
"sections": {$ doc.sections | json | indent(2) | trim $}
}

161
docs/templates/class.template.html vendored Normal file
View File

@ -0,0 +1,161 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
{% extends 'layout/base.template.html' -%}
{% block body %}
{% include "layout/_what-it-does.html" %}
{% include "layout/_security-notes.html" %}
{% include "layout/_deprecated-notes.html" %}
{% include "layout/_how-to-use.html" %}
<div layout="row" layout-xs="column" class="ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Class Overview</h2>
</div>
<div flex="80" flex-xs="100">
<code class="no-bg api-doc-code openParens">class {$ doc.name $} {</code>
{% if doc.statics.length %}
<div layout="column">
{% for member in doc.statics %}{% if not member.internal %}
<pre class="prettyprint no-bg-with-indent">static
<a class="code-anchor" href="#{$ member.name $}-anchor">
<code(class="code-background api-doc-code">{$ member.name | indent(6, false) | trim $}</code>
</a>
<code class="api-doc-code">
{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}
</code>
{% endif %}{% endfor %}
{% endif %}
{% if doc.constructorDoc.name %}
<div layout="column">
<pre class="prettyprint no-bg-with-indent">
<a class="code-anchor" href="#constructor">
<code class="code-background api-doc-code">{$ doc.constructorDoc.name $}</code>
</a>
<code class="api-doc-code">
{$ params.paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
</code>
{% endif %}
{% if doc.members.length %}
<div layout="column">
{% for member in doc.members %}{% if not member.internal %}
<pre class="prettyprint no-bg-with-indent">
<a class="code-anchor" href="#{$ member.name $}-anchor">
<code class="code-background api-doc-code">{$ member.name | indent(6, false) | trim $}</code>
</a>
<code class="api-doc-code">{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}</code>
</pre>
{% endif %}{% endfor %}
{% endif %}
<p class="selector endParens">
<code class="api-doc-code no-bg">}</code>
</p>
{% block additional %}
{% endblock %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Class Description</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{%- if doc.description.length > 2 %}
{$ doc.description | trimBlankLines | marked $}
{% endif %}
</div>
{%- if doc.decorators.length %}
{% block annotations %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Annotations</h2>
</div>
<div flex="80" flex-xs="100">
{%- for decorator in doc.decorators %}
<pre class="prettyprint no-bg">
<code class="api-doc-code">
@{$ decorator.name $}{$ params.paramList(decorator.arguments) | indent(10, false) $}
</code>
</pre>
{%- if not decorator.notYetDocumented %}
{$ decorator.description | indentForMarkdown(8) | trimBlankLines | marked $}
{% endif %}
{% endfor %}
</div>
{% endblock %}
{% endif %}
{%- if doc.constructorDoc and not doc.constructorDoc.internal %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Constructor</h2>
</div>
<div flex="80" flex-xs="100">
<a name="constructor" class="anchor-offset"></a>
<pre class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ doc.constructorDoc.name $}') }">
<code class="api-doc-code">
{$ doc.constructorDoc.name $}{$ params.paramList(doc.constructorDoc.parameters) | indent(8, false) | trim $}
</code>
</pre>
{%- if not doc.constructorDoc.notYetDocumented %}
{$ doc.constructorDoc.description | replace('### Example', '') | replace('## Example', '') | replace('# Example', '') | trimBlankLines | marked $}
{% endif %}
{% endif %}
{% if doc.statics.length %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Static Members</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{% for member in doc.statics %}{% if not member.internal %}
<a name="{$ member.name $}-anchor" class="anchor-offset"></a>
<pre class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ member.name $}') }">
<code class="api-doc-code">
{$ member.name $}{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}
</code>
</pre>
{%- if not member.notYetDocumented %}
{$ member.description | replace('### Example', '') | replace('## Example', '') | replace('# Example', '') | trimBlankLines | marked $}
{% endif %}
{% if not loop.last %}
<hr class="hr-margin">
{% endif %}
{% endif %}{% endfor %}
{% endif %}
{% if doc.members.length %}
<div layout="row" layout-xs="column" class="instance-members" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Class Details</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{% for member in doc.members %}{% if not member.internal %}
<a name="{$ member.name $}-anchor" class="anchor-offset">
<pre class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ member.name $}') }">
<code class="api-doc-code">
{$ member.name $}{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}
</code>
</pre>
{%- if not member.notYetDocumented %}
{$ member.description | replace('### Example', '') | replace('## Example', '') | replace('# Example', '') | trimBlankLines | marked $}
{% endif -%}
{% if not loop.last %}
<hr class="hr-margin">
{% endif %}
{% endif %}{% endfor %}
{% endif %}
<p class="location-badge">
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} },
defined in {$ github.githubViewLink(doc, versionInfo) $}
</p>
{% endblock %}

1
docs/templates/const.template.html vendored Normal file
View File

@ -0,0 +1 @@
{% extends 'var.template.html' -%}

View File

@ -0,0 +1 @@
export const {$ doc.serviceName $} = {$ doc.value | json $};

44
docs/templates/decorator.template.html vendored Normal file
View File

@ -0,0 +1,44 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
{% extends 'layout/base.template.html' %}
{% block body %}
{% include "layout/_what-it-does.html" %}
{% include "layout/_security-notes.html" %}
{% include "layout/_deprecated-notes.html" %}
{% include "layout/_how-to-use.html" %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Description</h2>
<div class="code-links" flex="80" flex-xs="100">
{%- if not doc.notYetDocumented %}{$ doc.description | trimBlankLines | marked $}{% endif %}
</div>
</div>
{% if doc.metadataDoc and doc.metadataDoc.members.length %}
<div layout="row" layout-xs="column" class="metadata" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Metadata Properties</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{% for metadata in doc.metadataDoc.members %}{% if not metadata.internal %}
<a name="{$ metadata.name $}-anchor" class="anchor-offset"></a>
<pre class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ metadata.name $}') }">
<code class="api-doc-code">
{$ metadata.name $}{$ params.paramList(metadata.parameters) | indent(8, false) | trim $}{$ params.returnType(metadata.returnType) $}
</code>
</pre>
{%- if not metadata.notYetDocumented %}{$ metadata.description | replace('### Example', '') | replace('## Example', '') | replace('# Example', '') | trimBlankLines | marked $}{% endif -%}
{% if not loop.last %}<hr class="hr-margin">{% endif %}
{% endif %}{% endfor %}
{% endif %}
<p class="location-badge">
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } defined in {$ github.githubViewLink(doc, versionInfo) $}
</p>
{% endblock %}

62
docs/templates/directive.template.html vendored Normal file
View File

@ -0,0 +1,62 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
{% extends 'class.template.html' -%}
{% block annotations %}
{% endblock %}
{% block additional -%}
{%- if doc.directiveOptions.selector.split(',').length %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Selectors</h2>
<div flex="80" flex-xs="100">
{% for selector in doc.directiveOptions.selector.split(',') %}
<p class="selector">
<code>{$ selector $}</code>
</p>
{% endfor %}
{% endif %}
{% if doc.outputs %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Outputs</h2>
</div>
<div flex="80" flex-xs="100">
{% for binding, property in doc.outputs %}
<div class="code-margin">
<code>{$ property.bindingName $}&nbsp;bound to&nbsp;</code>
<code>{$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}</code
</div>
{$ property.memberDoc.description | trimBlankLines | marked $}
</div>
{% endfor %}
</div>
{% endif %}
{% if doc.inputs %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Inputs</h2>
<div flex="80" flex-xs="100">
{% for binding, property in doc.inputs %}
<div class="code-margin">
<code>{$ property.bindingName $}&nbsp;bound to&nbsp;</code>
<code>{$ property.memberDoc.classDoc.name $}.{$ property.propertyName $}</code>
{$ property.memberDoc.description | trimBlankLines | marked $}
{% endfor %}
{% endif %}
{%- if doc.directiveOptions.exportAs %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Exported as</h2>
</div>
<div flex="80" flex-xs="100">
<p class="input">
<code>{$ doc.directiveOptions.exportAs $}</code>
</p>
{% endif %}
{% endblock %}

1
docs/templates/enum.template.html vendored Normal file
View File

@ -0,0 +1 @@
{% extends 'class.template.html' -%}

View File

@ -0,0 +1,7 @@
<div class="code-example">
{% marked %}
```
{$ doc.contents $}
```
{% endmarked %}
</div>

31
docs/templates/function.template.html vendored Normal file
View File

@ -0,0 +1,31 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
{% extends 'layout/base.template.html' -%}
{% block body %}
{% include "layout/_what-it-does.html" %}
{% include "layout/_security-notes.html" %}
{% include "layout/_deprecated-notes.html" %}
{% include "layout/_how-to-use.html" %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Class Export</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
<pre class="prettyprint no-bg">
<code>
export {$ doc.name $}{$ params.paramList(doc.parameters) | indent(8, true) | trim $}{$ params.returnType(doc.returnType) $}
</code>
</pre>
{%- if not doc.notYetDocumented %}{$ doc.description | trimBlankLines | marked $}{% endif %}
<p class="location-badge">
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} } defined in {$ github.githubViewLink(doc, versionInfo) $}
</p>
{% endblock %}

72
docs/templates/interface.template.html vendored Normal file
View File

@ -0,0 +1,72 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
{% extends 'layout/base.template.html' -%}
{% block body %}
{% include "layout/_what-it-does.html" %}
{% include "layout/_security-notes.html" %}
{% include "layout/_deprecated-notes.html" %}
{% include "layout/_how-to-use.html" %}
<div layout="row" layout-xs="column" class="ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Interface Overview</h2>
</div>
<div flex="80" flex-xs="100">
<code class="no-bg api-doc-code openParens">interface {$ doc.name $} {</code>
{% if doc.members.length %}
<div layout="column">
{% for member in doc.members %}{% if not member.internal %}
<pre class="prettyprint no-bg-with-indent">
<a class="code-anchor" href="#{$ member.name $}-anchor">
<code class="code-background api-doc-code">{$ member.name | indent(6, false) | trim $}</code>
<code class="api-doc-code">{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}</code>
</a>
</pre>
{% endif %}{% endfor %}
{% endif %}
<p class="selector endParens">
<code class="api-doc-code no-bg">}</code>
</p>
{% block additional %}
{% endblock %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Interface Description</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{%- if doc.description.length > 2 %}{$ doc.description | trimBlankLines | marked $}{% endif %}
</div>
{% if doc.members.length %}
<div layout="row" layout-xs="column" class="instance-members" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Interface Details</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{% for member in doc.members %}{% if not member.internal %}
<a name="{$ member.name $}-anchor" class="anchor-offset"></a>
<pre class="prettyprint no-bg" ng-class="{ 'anchor-focused': appCtrl.isApiDocMemberFocused('{$ member.name $}') }">
<code class="api-doc-code">
{$ member.name $}{$ params.paramList(member.parameters) | indent(8, false) | trim $}{$ params.returnType(member.returnType) $}
</code>
</pre>
{%- if not member.notYetDocumented %}{$ member.description | replace('### Example', '') | replace('## Example', '') | replace('# Example', '') | trimBlankLines | marked $}{% endif -%}
{% if not loop.last %}<hr class="hr-margin">{% endif %}
</div>
{% endif %}{% endfor %}
</div>
{% endif %}
<p class="location-badge">
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} },
defined in {$ github.githubViewLink(doc, versionInfo) $}
</p>
{% endblock %}

1
docs/templates/json-doc.template.json vendored Normal file
View File

@ -0,0 +1 @@
{$ doc.data | json $}

View File

@ -0,0 +1,11 @@
{% if doc.showDeprecatedNotes %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Deprecation Notes</h2>
</div>
<div flex="80" flex-xs="100">
{%- if doc.deprecated %}{$ doc.deprecated | marked $}
{% else %}<em>Not yet documented</em>{% endif %}
</div>
</div>
{% endif %}

10
docs/templates/layout/_how-to-use.html vendored Normal file
View File

@ -0,0 +1,10 @@
{%- if doc.howToUse %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">How to use</h2>
</div>
<div flex="80" flex-xs="100">
{$ doc.howToUse | marked $}
</div>
</div>
{% endif %}

8
docs/templates/layout/_ng-module.html vendored Normal file
View File

@ -0,0 +1,8 @@
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">NgModule</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{$ doc.ngModule $}
</div>
</div>

View File

@ -0,0 +1,10 @@
{% if doc.showSecurityNotes and doc.security %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Security Risk</h2>
</div>
<div flex="80" flex-xs="100">
{$ doc.security | marked $}
</div>
</div>
{% endif %}

View File

@ -0,0 +1,10 @@
{%- if doc.whatItDoes %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">What it does</h2>
</div>
<div flex="80" flex-xs="100">
{$ doc.whatItDoes | marked $}
</div>
</div>
{% endif %}

View File

@ -0,0 +1 @@
{% block body %}{% endblock %}

1
docs/templates/let.template.html vendored Normal file
View File

@ -0,0 +1 @@
{% extends 'var.template.html' -%}

7
docs/templates/lib/githubLinks.html vendored Normal file
View File

@ -0,0 +1,7 @@
{% macro githubHref(doc, versionInfo) -%}
https://github.com/{$ versionInfo.gitRepoInfo.owner $}/{$ versionInfo.gitRepoInfo.repo $}/tree/{$ versionInfo.currentVersion.isSnapshot and versionInfo.currentVersion.SHA or versionInfo.currentVersion.raw $}/modules/{$ doc.fileInfo.projectRelativePath $}#L{$ doc.location.start.line+1 $}-L{$ doc.location.end.line+1 $}
{%- endmacro %}
{% macro githubViewLink(doc, versionInfo) -%}
<a href="{$ githubHref(doc, versionInfo) $}">{$ doc.fileInfo.projectRelativePath $}</a>
{%- endmacro %}

12
docs/templates/lib/paramList.html vendored Normal file
View File

@ -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 -%}

14
docs/templates/module.template.html vendored Normal file
View File

@ -0,0 +1,14 @@
{% import "lib/githubLinks.html" as github -%}
{% extends 'layout/base.template.html' -%}
{% block body -%}
<p class="location-badge">defined in {$ github.githubViewLink(doc, versionInfo) $}</p>
<ul>
{% for page in doc.childPages -%}
<li>
<!-- TODO: convert to hovercard -->
<a href="{$ relativePath(doc.moduleFolder, page.exportDoc.path) $}">{$ page.title $}</a>
</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,74 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
body {
max-width: 1000px;
}
h2 {
margin-top: 20px;
margin-bottom: 0;
border-bottom: solid 1px black;
}
h3 {
margin-top: 10px;
margin-bottom: 0;
padding-left: 20px;
}
h4 {
padding-left: 30px;
margin: 0;
}
.not-documented::after {
content: "(not documented)";
font-size: small;
font-style: italic;
color: red;
}
a {
color: black;
text-decoration: none;
}
</style>
</head>
<body>
<h1>Module Overview</h1>
{% for module in doc.modules %}
<h2>
<code>{$ module.id $}{%- if module.public %} (public){% endif %}</code>
</h2>
{% for export in module.exports %}
<h3 {% if export.notYetDocumented %}class="not-documented"{% endif %}>
<a href="{$ github.githubHref(export, versionInfo) $}">
<code>{$ export.docType $} {$ export.name $}</code>
</a>
</h3>
{%- if export.constructorDoc %}
<h4 {% if export.constructorDoc.notYetDocumented %}class="not-documented"{% endif %}>
<a href="{$ github.githubHref(export.constructorDoc, versionInfo) $}">
<code>{$ export.constructorDoc.name $}{$ params.paramList(export.constructorDoc.params) $}</code>
</a>
</h4>
{% endif -%}
{%- for member in export.members %}
<h4 {% if member.notYetDocumented %}class="not-documented"{% endif %}>
<a href="{$ github.githubHref(member, versionInfo) $}">
<code>{$ member.name $}{$ params.paramList(member.params) $}</code>
</a>
</h4>
{% endfor %}
{% endfor %}
{% endfor %}
</body>
</html>

30
docs/templates/pipe.template.html vendored Normal file
View File

@ -0,0 +1,30 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
{% extends 'layout/base.template.html' -%}
{% block body %}
{% include "layout/_what-it-does.html" %}
{% include "layout/_security-notes.html" %}
{% include "layout/_deprecated-notes.html" %}
{% include "layout/_how-to-use.html" %}
{% include "layout/_ng-module.html" %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Description</h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
{%- if doc.description.length > 2 %}{$ doc.description | trimBlankLines | marked $}{% endif %}
</div>
</div>
<p class="location-badge">
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
defined in {$ github.githubViewLink(doc, versionInfo) $}
</p>
{% endblock %}

View File

@ -0,0 +1 @@
{% extends 'interface.template.html' %}

33
docs/templates/var.template.html vendored Normal file
View File

@ -0,0 +1,33 @@
{% import "lib/githubLinks.html" as github -%}
{% import "lib/paramList.html" as params -%}
{% extends 'layout/base.template.html' %}
{% block body %}
{% include "layout/_what-it-does.html" %}
{% include "layout/_security-notes.html" %}
{% include "layout/_deprecated-notes.html" %}
{% include "layout/_how-to-use.html" %}
<div layout="row" layout-xs="column" class="row-margin ng-cloak">
<div flex="20" flex-xs="100">
<h2 class="h2-api-docs">Variable Export<h2>
</div>
<div class="code-links" flex="80" flex-xs="100">
<pre class="prettyprint no-bg">
<code>
export {$ doc.name $}
</code>
</pre>
{%- if not doc.notYetDocumented %}{$ doc.description | trimBlankLines | marked $}{% endif %}
</div>
</div>
<p class="location-badge">
exported from {@link {$ doc.moduleDoc.id $} {$doc.moduleDoc.id $} }
defined in {$ github.githubViewLink(doc, versionInfo) $}
</p>
{% endblock %}

View File

@ -252,6 +252,24 @@ gulp.task('changelog', () => {
.pipe(gulp.dest('./'));
});
gulp.task('docs', ['doc-gen', 'docs-app']);
gulp.task('doc-gen', () => {
const Dgeni = require('dgeni');
const angularDocsPackage = require(path.resolve(__dirname, 'tools/docs/angular.io-package'));
const dgeni = new Dgeni([angularDocsPackage]);
return dgeni.generate();
});
gulp.task('docs-app', () => { gulp.src('docs/src/**/*').pipe(gulp.dest('dist/docs')); });
gulp.task('docs-test', ['doc-gen-test', 'docs-app-test']);
gulp.task('doc-gen-test', () => {
const execSync = require('child_process').execSync;
execSync(
'node dist/tools/cjs-jasmine/index-tools ../../tools/docs/**/*.spec.js',
{stdio: ['inherit', 'inherit', 'inherit']});
});
gulp.task('docs-app-test', () => {});
function tsc(projectPath, done) {
const childProcess = require('child_process');

27
tools/docs/README.md Normal file
View File

@ -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`

View File

@ -0,0 +1,701 @@
a
able
about
above
abst
accordance
according
accordingly
across
act
actually
added
adj
adopted
affected
affecting
affects
after
afterwards
again
against
ah
all
almost
alone
along
already
also
although
always
am
among
amongst
an
and
announce
another
any
anybody
anyhow
anymore
anyone
anything
anyway
anyways
anywhere
apparently
approximately
are
aren
arent
arise
around
as
aside
ask
asking
at
auth
available
away
awfully
b
back
be
became
because
become
becomes
becoming
been
before
beforehand
begin
beginning
beginnings
begins
behind
being
believe
below
beside
besides
between
beyond
biol
both
brief
briefly
but
by
c
ca
came
can
cannot
can't
cant
cause
causes
certain
certainly
co
com
come
comes
contain
containing
contains
could
couldnt
d
date
did
didn't
didnt
different
do
does
doesn't
doesnt
doing
done
don't
dont
down
downwards
due
during
e
each
ed
edu
effect
eg
eight
eighty
either
else
elsewhere
end
ending
enough
especially
et
et-al
etc
even
ever
every
everybody
everyone
everything
everywhere
ex
except
f
far
few
ff
fifth
first
five
fix
followed
following
follows
for
former
formerly
forth
found
four
from
further
furthermore
g
gave
get
gets
getting
give
given
gives
giving
go
goes
gone
got
gotten
h
had
happens
hardly
has
hasn't
hasnt
have
haven't
havent
having
he
hed
hence
her
here
hereafter
hereby
herein
heres
hereupon
hers
herself
hes
hi
hid
him
himself
his
hither
home
how
howbeit
however
hundred
i
id
ie
if
i'll
ill
im
immediate
immediately
importance
important
in
inc
indeed
index
information
instead
into
invention
inward
is
isn't
isnt
it
itd
it'll
itll
its
itself
i've
ive
j
just
k
keep
keeps
kept
keys
kg
km
know
known
knows
l
largely
last
lately
later
latter
latterly
least
less
lest
let
lets
like
liked
likely
line
little
'll
'll
look
looking
looks
ltd
m
made
mainly
make
makes
many
may
maybe
me
mean
means
meantime
meanwhile
merely
mg
might
million
miss
ml
more
moreover
most
mostly
mr
mrs
much
mug
must
my
myself
n
na
name
namely
nay
nd
near
nearly
necessarily
necessary
need
needs
neither
never
nevertheless
new
next
nine
ninety
no
nobody
non
none
nonetheless
noone
nor
normally
nos
not
noted
nothing
now
nowhere
o
obtain
obtained
obviously
of
off
often
oh
ok
okay
old
omitted
on
once
one
ones
only
onto
or
ord
other
others
otherwise
ought
our
ours
ourselves
out
outside
over
overall
owing
own
p
page
pages
part
particular
particularly
past
per
perhaps
placed
please
plus
poorly
possible
possibly
potentially
pp
predominantly
present
previously
primarily
probably
promptly
proud
provides
put
q
que
quickly
quite
qv
r
ran
rather
rd
re
readily
really
recent
recently
ref
refs
regarding
regardless
regards
related
relatively
research
respectively
resulted
resulting
results
right
run
s
said
same
saw
say
saying
says
sec
section
see
seeing
seem
seemed
seeming
seems
seen
self
selves
sent
seven
several
shall
she
shed
she'll
shell
shes
should
shouldn't
shouldnt
show
showed
shown
showns
shows
significant
significantly
similar
similarly
since
six
slightly
so
some
somebody
somehow
someone
somethan
something
sometime
sometimes
somewhat
somewhere
soon
sorry
specifically
specified
specify
specifying
state
states
still
stop
strongly
sub
substantially
successfully
such
sufficiently
suggest
sup
sure
t
take
taken
taking
tell
tends
th
than
thank
thanks
thanx
that
that'll
thatll
thats
that've
thatve
the
their
theirs
them
themselves
then
thence
there
thereafter
thereby
thered
therefore
therein
there'll
therell
thereof
therere
theres
thereto
thereupon
there've
thereve
these
they
theyd
they'll
theyll
theyre
they've
theyve
think
this
those
thou
though
thoughh
thousand
throug
through
throughout
thru
thus
til
tip
to
together
too
took
toward
towards
tried
tries
truly
try
trying
ts
twice
two
u
un
under
unfortunately
unless
unlike
unlikely
until
unto
up
upon
ups
us
use
used
useful
usefully
usefulness
uses
using
usually
v
value
various
've
've
very
via
viz
vol
vols
vs
w
want
wants
was
wasn't
wasnt
way
we
wed
welcome
we'll
well
went
were
weren't
werent
we've
weve
what
whatever
what'll
whatll
whats
when
whence
whenever
where
whereafter
whereas
whereby
wherein
wheres
whereupon
wherever
whether
which
while
whim
whither
who
whod
whoever
whole
who'll
wholl
whom
whomever
whos
whose
why
widely
will
willing
wish
with
within
without
won't
wont
words
would
wouldn't
wouldnt
www
x
y
yes
yet
you
youd
you'll
youll
your
youre
yours
yourself
yourselves
you've
youve
z
zero

View File

@ -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)));
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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);
}
}
});
}
};
};

View File

@ -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);
});
});

View File

@ -0,0 +1,11 @@
module.exports = function convertPrivateClassesToInterfacesProcessor(
convertPrivateClassesToInterfaces) {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
$process: function(docs) {
convertPrivateClassesToInterfaces(docs, false);
return docs;
}
};
};

View File

@ -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);
}
};
};

View File

@ -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;
}
};
};

View File

@ -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'}
}));
});
});

View File

@ -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];
}
}

View File

@ -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'; });
}
}
};

View File

@ -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;
}

View File

@ -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);
}
};
};

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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');
});
});

View File

@ -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;
};
};

View File

@ -0,0 +1,6 @@
module.exports = function toId() {
return {
name: 'toId',
process: function(str) { return str.replace(/[^(a-z)(A-Z)(0-9)._-]/g, '-'); }
};
};

View File

@ -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');
});
});

View File

@ -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');
}
};
};

View File

@ -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');
});
});

View File

@ -0,0 +1,4 @@
// A ts2dart compiler annotation that can be ignored in API docs.
module.exports = function() {
return {name: 'Annotation', ignore: true};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'deprecated'};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'docsNotRequired'};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'experimental'};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'howToUse'};
};

View File

@ -0,0 +1,5 @@
module.exports = function() {
return {
name: 'internal', transforms: function() { return true; }
}
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'ngModule'};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'noDescription', transforms: function() { return true; }};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'security'};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'stable'};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'syntax'};
};

View File

@ -0,0 +1,4 @@
// A ts2dart compiler annotation that can be ignored in API docs.
module.exports = function() {
return {name: 'ts2dart_const', ignore: true};
};

View File

@ -0,0 +1,3 @@
module.exports = function() {
return {name: 'whatItDoes'};
};

View File

@ -0,0 +1,16 @@
var Package = require('dgeni').Package;
module.exports = new Package(
'cheatsheet',
[
require('../content-package'), require('../target-package'),
require('dgeni-packages/git'), require('dgeni-packages/nunjucks')
])
.factory(require('./services/cheatsheetItemParser'))
.processor(require('./processors/createCheatsheetDoc'))
.config(function(parseTagsProcessor, getInjectables) {
parseTagsProcessor.tagDefinitions = parseTagsProcessor.tagDefinitions.concat(
getInjectables(require('./tag-defs')));
});

View File

@ -0,0 +1,48 @@
var _ = require('lodash');
module.exports = function createCheatsheetDoc(
createDocMessage, renderMarkdown, versionInfo, targetEnvironments) {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
$process: function(docs) {
var currentEnvironment = targetEnvironments.isActive('ts') && 'TypeScript' ||
targetEnvironments.isActive('js') && 'JavaScript' ||
targetEnvironments.isActive('dart') && 'Dart';
var cheatsheetDoc = {
id: 'cheatsheet',
aliases: ['cheatsheet'],
docType: 'cheatsheet-data',
sections: [],
version: versionInfo,
currentEnvironment: currentEnvironment
};
docs = docs.filter(function(doc) {
if (doc.docType === 'cheatsheet-section') {
var section = _.pick(doc, ['name', 'description', 'items', 'index']);
// Let's make sure that the descriptions are rendered as markdown
section.description = renderMarkdown(section.description);
section.items.forEach(function(item) {
item.description = renderMarkdown(item.description);
});
cheatsheetDoc.sections.push(section);
return false;
}
return true;
});
// Sort the sections by their index
cheatsheetDoc.sections.sort(function(a, b) { return a.index - b.index; });
docs.push(cheatsheetDoc);
return docs;
}
};
};

View File

@ -0,0 +1,125 @@
/**
* @dgService
* @description
* Parse the text from a cheatsheetItem tag into a cheatsheet item object
* The text must contain a syntax block followed by zero or more bold matchers and finally a
* description
* The syntax block and bold matchers must be wrapped in backticks and be separated by pipes.
* For example
*
* ```
* `<div [ng-switch]="conditionExpression">
* <template [ng-switch-when]="case1Exp">...</template>
* <template ng-switch-when="case2LiteralString">...</template>
* <template ng-switch-default>...</template>
* </div>`|`[ng-switch]`|`[ng-switch-when]`|`ng-switch-when`|`ng-switch-default`
* Conditionally swaps the contents of the div by selecting one of the embedded templates based on
* the current value of conditionExpression.
* ```
*
* will be parsed into
*
* ```
* {
* syntax: '<div [ng-switch]="conditionExpression">\n'+
* ' <template [ng-switch-when]="case1Exp">...</template>\n'+
* ' <template ng-switch-when="case2LiteralString">...</template>\n'+
* ' <template ng-switch-default>...</template>\n'+
* '</div>',
* bold: ['[ng-switch]', '[ng-switch-when]', 'ng-switch-when', 'ng-switch-default'],
* description: 'Conditionally swaps the contents of the div by selecting one of the embedded
* templates based on the current value of conditionExpression.'
* }
* ```
*/
module.exports =
function cheatsheetItemParser(targetEnvironments) {
return function(text) {
var fields = getFields(text, ['syntax', 'description']);
var item = {syntax: '', bold: [], description: ''};
fields.forEach(function(field) {
if (!field.languages || targetEnvironments.someActive(field.languages)) {
switch (field.name) {
case 'syntax':
parseSyntax(field.value.trim());
break;
case 'description':
item.description = field.value.trim();
break;
}
}
});
return item;
function parseSyntax(text) {
var index = 0;
if (text.charAt(index) !== '`') throw new Error('item syntax must start with a backtick');
var start = index + 1;
index = text.indexOf('`', start);
if (index === -1) throw new Error('item syntax must end with a backtick');
item.syntax = text.substring(start, index);
start = index + 1;
// skip to next pipe
while (index < text.length && text.charAt(index) !== '|') index += 1;
while (text.charAt(start) === '|') {
start += 1;
// skip whitespace
while (start < text.length && /\s/.test(text.charAt(start))) start++;
if (text.charAt(start) !== '`') throw new Error('bold matcher must start with a backtick');
start += 1;
index = text.indexOf('`', start);
if (index === -1) throw new Error('bold matcher must end with a backtick');
item.bold.push(text.substring(start, index));
start = index + 1;
}
if (start !== text.length) {
throw new Error(
'syntax field must only contain a syntax code block and zero or more bold ' +
'matcher code blocks, delimited by pipes.\n' +
'Instead it was "' + text + '"');
}
}
};
}
function getFields(text, fieldNames) {
var FIELD_START = /^([^:(]+)\(?([^)]+)?\)?:$/;
var lines = text.split('\n');
var fields = [];
var field, line;
while (lines.length) {
line = lines.shift();
var match = FIELD_START.exec(line);
if (match && fieldNames.indexOf(match[1]) !== -1) {
// start new field
if (field) {
fields.push(field);
}
field = {name: match[1], languages: (match[2] && match[2].split(' ')), value: ''};
} else {
if (!field)
throw new Error(
'item must start with one of the following field specifiers:\n' +
fieldNames.map(function(field) { return field + ':'; }).join('\n') + '\n' +
'but instead it contained: "' + text + '"');
field.value += line + '\n';
}
}
if (field) {
fields.push(field);
}
return fields;
}

View File

@ -0,0 +1,75 @@
var testPackage = require('../../helpers/test-package');
var Dgeni = require('dgeni');
describe('cheatsheetItemParser', function() {
var dgeni, injector, cheatsheetItemParser;
beforeEach(function() {
dgeni = new Dgeni([testPackage('cheatsheet-package')]);
injector = dgeni.configureInjector();
cheatsheetItemParser = injector.get('cheatsheetItemParser');
var targetEnvironments = injector.get('targetEnvironments');
targetEnvironments.addAllowed('js');
targetEnvironments.addAllowed('ts', true);
});
describe('no language targets', function() {
it('should extract the syntax', function() {
expect(cheatsheetItemParser('syntax:\n`abc`'))
.toEqual({syntax: 'abc', bold: [], description: ''});
});
it('should extract the bolds', function() {
expect(cheatsheetItemParser('syntax:\n`abc`|`bold1`|`bold2`'))
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: ''});
});
it('should extract the description', function() {
expect(cheatsheetItemParser('syntax:\n`abc`|`bold1`|`bold2`\ndescription:\nsome description'))
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: 'some description'});
});
it('should allow bold to be optional', function() {
expect(cheatsheetItemParser('syntax:\n`abc`\ndescription:\nsome description'))
.toEqual({syntax: 'abc', bold: [], description: 'some description'});
});
it('should allow whitespace between the parts', function() {
expect(cheatsheetItemParser(
'syntax:\n`abc`| `bold1`| `bold2`\ndescription:\n\nsome description'))
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: 'some description'});
});
});
describe('with language targets', function() {
it('should extract the active language', function() {
expect(cheatsheetItemParser(
'syntax(ts):\n`abc`|`bold1`|`bold2`\ndescription(ts):\nsome description'))
.toEqual({syntax: 'abc', bold: ['bold1', 'bold2'], description: 'some description'});
});
it('should ignore the non-active language', function() {
expect(cheatsheetItemParser(
'syntax(js):\n`abc`|`bold1`|`bold2`\ndescription(js):\nsome description'))
.toEqual({syntax: '', bold: [], description: ''});
});
it('should select the active language and ignore non-active language', function() {
expect(cheatsheetItemParser(
'syntax(js):\n`JS`|`boldJS``\n' +
'syntax(ts):\n`TS`|`boldTS`\n' +
'description(js):\nJS description\n' +
'description(ts):\nTS description'))
.toEqual({syntax: 'TS', bold: ['boldTS'], description: 'TS description'});
});
it('should error if a language target is used that is not allowed', function() {
expect(function() {
cheatsheetItemParser(
'syntax(dart):\n`abc`|`bold1`|`bold2`\ndescription(ts):\nsome description');
})
.toThrowError(
'Error accessing target "dart". It is not in the list of allowed targets: js,ts');
});
});
});

View File

@ -0,0 +1,14 @@
module.exports = function(createDocMessage) {
return {
name: 'cheatsheetIndex',
docProperty: 'index',
transforms: function(doc, tag, value) {
try {
return parseInt(value, 10);
} catch (x) {
throw new Error(
createDocMessage('"@' + tag.tagName + '" must be followed by a number', doc));
}
}
};
};

View File

@ -0,0 +1,15 @@
module.exports = function(createDocMessage, cheatsheetItemParser) {
return {
name: 'cheatsheetItem',
multi: true,
docProperty: 'items',
transforms: function(doc, tag, value) {
try {
return cheatsheetItemParser(value);
} catch (x) {
throw new Error(createDocMessage(
'"@' + tag.tagName + '" tag has an invalid format - ' + x.message, doc));
}
}
};
};

View File

@ -0,0 +1,10 @@
module.exports = function() {
return {
name: 'cheatsheetSection',
docProperty: 'docType',
transforms: function(doc, tag, value) {
doc.name = value ? value.trim() : '';
return 'cheatsheet-section';
}
};
};

View File

@ -0,0 +1,2 @@
module.exports =
[require('./cheatsheet-section'), require('./cheatsheet-index'), require('./cheatsheet-item')];

View File

@ -0,0 +1,35 @@
var Package = require('dgeni').Package;
var jsdocPackage = require('dgeni-packages/jsdoc');
var linksPackage = require('../links-package');
var path = require('canonical-path');
var fs = require('fs');
// Define the dgeni package for generating the docs
module.exports = new Package('content', [jsdocPackage, linksPackage])
// Register the services and file readers
.factory(require('./readers/content'))
// Configure file reading
.config(function(readFilesProcessor, contentFileReader) {
readFilesProcessor.fileReaders.push(contentFileReader);
})
// Configure ids and paths
.config(function(computeIdsProcessor, computePathsProcessor) {
computeIdsProcessor.idTemplates.push({
docTypes: ['content'],
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]; }
});
});

View File

@ -0,0 +1,26 @@
var path = require('canonical-path');
/**
* @dgService
* @description
* This file reader will pull the contents from a text file (by default .md)
*
* The doc will initially have the form:
* ```
* {
* content: 'the content of the file',
* startingLine: 1
* }
* ```
*/
module.exports = function contentFileReader() {
return {
name: 'contentFileReader',
defaultPattern: /\.md$/,
getDocs: function(fileInfo) {
// We return a single element array because content files only contain one document
return [{docType: 'guide', content: fileInfo.content, startingLine: 1}];
}
};
};

View File

@ -0,0 +1,44 @@
var testPackage = require('../../helpers/test-package');
var Dgeni = require('dgeni');
var path = require('canonical-path');
describe('contentFileReader', function() {
var dgeni, injector, fileReader;
beforeEach(function() {
dgeni = new Dgeni([testPackage('content-package', true)]);
injector = dgeni.configureInjector();
fileReader = injector.get('contentFileReader');
});
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
};
};
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}
]);
});
});
});

11
tools/docs/eslintrc.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
'globals': {'describe': true, 'beforeEach': true, 'it': true, 'expect': true},
'env': {'node': true},
'extends': 'eslint:recommended',
'rules': {
'indent': ['error', 2],
'linebreak-style': ['error', 'unix'],
'quotes': ['error', 'single'],
'semi': ['error', 'always']
}
};

View File

@ -0,0 +1,14 @@
/**
* The point of this reader is to tag all the files that are going to be used as examples in the
* documentation.
* Later on we can extract the regions, via "shredding"; and we can also construct runnable examples
* for passing to plunker and the like.
*/
module.exports = function exampleFileReader(log) {
return {
name: 'exampleFileReader',
getDocs: function(fileInfo) {
return [{docType: 'example-file', content: fileInfo.content, startingLine: 1}];
}
};
};

View File

@ -0,0 +1,29 @@
var Package = require('dgeni').Package;
var jsdocPackage = require('dgeni-packages/jsdoc');
module.exports =
new Package('examples', [jsdocPackage])
.factory(require('./inline-tag-defs/example'))
// .factory(require('./inline-tag-defs/exampleTabs'))
.factory(require('./services/parseArgString'))
.factory(require('./services/getExampleFilename'))
.factory(require('./services/example-map'))
.factory(require('./file-readers/example-reader'))
.factory(require('./services/region-parser'))
.processor(require('./processors/collect-examples'))
.config(function(readFilesProcessor, exampleFileReader) {
readFilesProcessor.fileReaders.push(exampleFileReader);
})
.config(function(inlineTagProcessor, exampleInlineTagDef) {
inlineTagProcessor.inlineTagDefinitions.push(exampleInlineTagDef);
// inlineTagProcessor.inlineTagDefinitions.push(exampleTabsInlineTagDef);
})
.config(function(computePathsProcessor) {
computePathsProcessor.pathTemplates.push(
{docTypes: ['example-region'], getPath: function() {}, getOutputPath: function() {}});
});

View File

@ -0,0 +1,57 @@
var path = require('canonical-path');
var fs = require('fs');
var entities = require('entities');
/**
* @dgService exampleInlineTagDef
* @description
* Process inline example tags (of the form {@example relativePath region -title='some title'
* -stylePattern='{some style pattern}' }),
* replacing them with code from a shredded file
* 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(
parseArgString, exampleMap, getExampleFilename, createDocMessage, log, collectExamples) {
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) {
const EXAMPLES_FOLDER = collectExamples.exampleFolders[0];
var tagArgs = parseArgString(entities.decodeHTML(tagDescription));
var unnamedArgs = tagArgs._;
var relativePath = unnamedArgs[0];
var regionName = tagArgs.region || (unnamedArgs.length > 1 ? unnamedArgs[1] : null);
var title = tagArgs.title || (unnamedArgs.length > 2 ? unnamedArgs[2] : null);
var stylePattern = tagArgs.stylePattern; // TODO: not yet implemented here
var exampleFile = exampleMap[EXAMPLES_FOLDER][relativePath];
if (!exampleFile) {
log.error(
createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc));
log.error(
'Example files available are:', Object.keys(exampleMap[EXAMPLES_FOLDER]).join('\n'));
return '';
}
var sourceCode = exampleFile.regions[regionName];
if (!sourceCode) {
log.error(createDocMessage(
'Missing example region... relativePath: "' + relativePath + '", region: "' +
regionName + '".',
doc));
log.error('Regions available are:', Object.keys[exampleFile.regions]);
return '';
}
return sourceCode.renderedContent;
}
};
};

Some files were not shown because too many files have changed in this diff Show More