Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Add techdocs to registry ui #111

Merged
merged 68 commits into from Oct 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
db8b2b8
WIP
Jul 23, 2018
551d62f
WIP
Jul 23, 2018
42b493e
WIP
Jul 23, 2018
b6efd06
WIP restructure JSDoc nav and add module type
Jul 24, 2018
931daf5
Tidy up JS Nodes
Jul 24, 2018
4c3c515
Add more JSDoc templates and comment code
Jul 24, 2018
6ffe5fb
Remove JSDoc footer
notlee Jul 26, 2018
bea8aaf
Show sassdoc example
notlee Jul 26, 2018
254e6da
Share example template with JSDoc and SassDoc
Jul 27, 2018
61ec059
Create mixin node
Jul 27, 2018
084bf21
WIP
Jul 27, 2018
fb664e6
fix sassdoc groups
Jul 27, 2018
90cda80
Refacor JSDoc
Jul 30, 2018
9b71b0d
Add paths and link to github
notlee Jul 30, 2018
d02bf7c
WIP sidebar & filtering
notlee Aug 1, 2018
e44f1e8
Readme with defined and generated nav
notlee Aug 2, 2018
53fa0be
Generate readme nav
notlee Aug 2, 2018
66a3589
Add filterable subnavs
notlee Aug 3, 2018
41d6d98
Implement nav filter feeling lucky
notlee Aug 3, 2018
877348c
Restore src/js/component-listing.js
notlee Aug 3, 2018
0d9cb94
Add readme class which shares a nav representation with docs
notlee Aug 3, 2018
7c5b18f
Add .DS_Store to gitignore
notlee Aug 3, 2018
7a43959
Remove .DS_Store
notlee Aug 3, 2018
dbeaccc
Merge branch 'master' into tech-docs
notlee Aug 3, 2018
41cd161
Update circle image
notlee Aug 3, 2018
47fe912
Verify issue (except test coverage :see_no_evil:)
notlee Aug 3, 2018
7d82962
Remove defunt file
notlee Aug 3, 2018
acf7f77
bump o-syntax
notlee Aug 6, 2018
75a7444
Fix code blocks where language declaration has spaces
notlee Aug 6, 2018
3a95fd0
Test nav-node
notlee Aug 7, 2018
d60fa9d
Add tests for JSDoc base node
notlee Aug 7, 2018
7e8089f
Add test for JSDOC class doclet
notlee Aug 7, 2018
1250924
Add test for JSDoc event node
notlee Aug 7, 2018
988780a
Fight circle cache
notlee Aug 7, 2018
5cd39a7
Add test for JSDoc function node
notlee Aug 7, 2018
f76900d
Add test for JSDoc mixin node
notlee Aug 7, 2018
4d36990
Add test for JSDoc module node
notlee Aug 7, 2018
103d50d
Add test for JSDoc namespace node
notlee Aug 7, 2018
087d9f9
Add test for JSDoc property node
notlee Aug 7, 2018
c906d1a
Correct property node template
notlee Aug 7, 2018
1ba71f6
Unit test JSDoc nav
notlee Aug 7, 2018
707b70d
Test JsDoc.formatDoclet
notlee Aug 8, 2018
147c55f
Test JsDoc.getNodes
notlee Aug 8, 2018
e75a38d
Add functional tests for code-docs/jsdoc/index
notlee Aug 8, 2018
77ca8b9
Remove unsued var
notlee Aug 8, 2018
c4cf1e2
Add functional test for sassdoc/index
notlee Aug 8, 2018
4989d24
WIP sassdoc base unit test
notlee Aug 8, 2018
daa4f2d
remove log
notlee Aug 8, 2018
93cd583
o-syntax-highlight inline code blocks
notlee Aug 9, 2018
650c6ab
sassdoc base unit test
notlee Aug 9, 2018
4e79d5b
Test sass doclets
notlee Aug 9, 2018
773f173
Test sassdoc getNodes removes expected doclets
notlee Aug 9, 2018
aa2a95c
Test sassdoc getNodesByKind
notlee Aug 9, 2018
40eff66
Test sass doclets are filtered by brand
notlee Aug 9, 2018
65fcb8a
Test sassdoc nav
notlee Aug 10, 2018
56792a6
Test readme
notlee Aug 10, 2018
324beb5
Add test for readme route (sass/jsdoc route to follow when using real…
notlee Aug 10, 2018
6148c46
remove codedocs sidebar
notlee Aug 10, 2018
55b56bf
Merge branch 'master' into tech-docs
notlee Oct 3, 2018
784de8f
Update package-lock and add snyk exception for 30 days
notlee Oct 3, 2018
f9cb441
Use origami codedocs app.
notlee Oct 5, 2018
1fbc2c4
try/catch
notlee Oct 5, 2018
4f633f5
Show default sassdoc/jsdoc.
notlee Oct 8, 2018
7981783
No longer run whitesource. We use snyk instead.
notlee Oct 8, 2018
41f3aac
Remove whitesource conf
notlee Oct 8, 2018
bdd4ca2
Handle empty JS/SassDocs
notlee Oct 8, 2018
eadd964
Hide JsDoc params within heading if there are many
notlee Oct 8, 2018
7d23275
Merge branch 'master' into tech-docs
notlee Oct 11, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
@@ -1,7 +1,9 @@
.DS_Store
.env
.nyc_output
coverage
node_modules/
npm-debug.log
public/
bower_components/
.vscode/
9 changes: 9 additions & 0 deletions .snyk
@@ -0,0 +1,9 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.12.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
'npm:mem:20180117':
- showdown > yargs > os-locale > mem:
reason: No patch avalible yet and DoS of registry README is not a high risk.
expires: '2018-11-02T17:09:51.357Z'
patch: {}
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -22,7 +22,7 @@ Get information about Origami components, services, and repositories.

## Requirements

Running Origami Registry UI requires [Node.js] 8.x and [npm].
Running Origami Registry UI requires [Node.js] 10.x and [npm].


## Running Locally
Expand Down
3 changes: 2 additions & 1 deletion bower.json
Expand Up @@ -17,11 +17,12 @@
"o-footer-services": "^2.0.1",
"o-forms": "^5.3.0",
"o-message": "^2.2.2",
"o-syntax-highlight": "^1.0.0",
"o-syntax-highlight": "^1.2.0",
"o-tabs": "^4.1.0",
"o-typography": "^5.6.0",
"o-normalise": "^1.6.2",
"o-overlay": "^2.4.1",
"o-table": "^6.8.1",
"o-visual-effects": "^2.0.3"
}
}
6 changes: 3 additions & 3 deletions circle.yml
Expand Up @@ -3,16 +3,16 @@ jobs:
test:
working_directory: ~/origami-registry-ui
docker:
- image: node:8
- image: node:10
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: node-10-dependency-cache-{{ checksum "package.json" }}
- run:
name: Install Node.js dependencies
command: npm install
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
key: node-10-dependency-cache-{{ checksum "package.json" }}
paths:
- node_modules
- run:
Expand Down
2 changes: 2 additions & 0 deletions index.js
Expand Up @@ -10,6 +10,8 @@ const options = {
log: console,
name: 'Origami Registry',
repoDataApiKey: process.env.REPO_DATA_API_KEY,
codedocsApiKey: process.env.CODEDOCS_API_KEY,
codedocsEndpoint: process.env.CODEDOCS_ENDPOINT,
repoDataApiSecret: process.env.REPO_DATA_API_SECRET,
workers: process.env.WEB_CONCURRENCY || 1
};
Expand Down
11 changes: 11 additions & 0 deletions lib/code-docs/example.js
@@ -0,0 +1,11 @@
'use strict';

class Example {
constructor(code, type = '', caption = '') {
this.caption = caption ? `${caption}\n` : ''; // newline to render as <p> with markdown
this.type = type || '';
this.code = code;
}
}

module.exports = Example;
116 changes: 116 additions & 0 deletions lib/code-docs/jsdoc/index.js
@@ -0,0 +1,116 @@
'use strict';

const ClassNode = require('./nodes/class');
const EventNode = require('./nodes/event');
const FunctionNode = require('./nodes/function');
const MixinNode = require('./nodes/mixin');
const NamespaceNode = require('./nodes/namespace');
const PropertyNode = require('./nodes/property');
const ModuleNode = require('./nodes/module');

/**
* Methods to prepare JsDoc json doclets for display within the registry.
*/
class JsDoc {
/**
* @param {Object[]} doclets - Raw json doclets generated by JSDoc.
*/
constructor(doclets) {
this.doclets = doclets;
}

/**
* @returns {string[]} Kinds of JSDoc doclets which are supported.
*/
static supportedDoclets() {
return ['class', 'function', 'constant', 'member', 'event', 'namespace', 'mixin', 'module'];
}

/**
* Format a doclet by removing superfluous properties and adding any custom properties.
* @param {Object} doclet - A raw json doclet generated by JSDoc.
* @returns {Object} Formatted node.
*/
static formatDoclet(doclet) {
switch (doclet.kind) {
case 'class':
return new ClassNode(doclet);
break;
case 'function':
return new FunctionNode(doclet);
break;
case 'constant':
case 'member':
return new PropertyNode(doclet);
break;
case 'event':
return new EventNode(doclet);
break;
case 'namespace':
return new NamespaceNode(doclet);
break;
case 'mixin':
return new MixinNode(doclet);
break;
case 'module':
return new ModuleNode(doclet);
break;
default:
throw new Error(`JsDoc doclet kind "${doclet.kind}" is not supported by the registry.`);
break;
};
}

/**
* @returns {Object[]} Formatted nodes.
*/
getNodes() {
if (!this._formattedDoclets) {
const supportedDoclets = this.doclets.filter((doclet) => {
const isNotIndicatedPrivate = doclet.name && doclet.name.indexOf('_') !== 0;
const isSupported = doclet.kind && JsDoc.supportedDoclets().includes(doclet.kind);
const isPublic = doclet.access !== 'private';
const isDocumented = doclet.undocumented !== true;
return isSupported && isPublic && isNotIndicatedPrivate && isDocumented;
});

this._formattedDoclets = supportedDoclets.map((doclet) => {
return JsDoc.formatDoclet(doclet);
});
}
return this._formattedDoclets;
}

/**
* @returns {Object[]} Formatted nodes.
*/
getNodesByTypeWithMembers() {
if (!this._formattedDocletsWithMembers) {
this._formattedDocletsWithMembers = JsDoc._addChildNodes(this.getNodes(), {});
}
return this._formattedDocletsWithMembers;
}

/**
* Add member nodes to parent nodes. E.g. add function nodes to their corresponding class node.
* @param {Object[]} nodes [{}] - Formatted JSDoc doclets.
* @param {Object|Object[]} parentNodes - Root nodes to add member nodes to (optional).
* @returns {Object[]} Nodes with member nodes grouped by kind e.g. {name: 'Example', type: class, examples: [{}], functions: [{}], properties: [{}]}
* @access private
*/
static _addChildNodes(nodes, parentNodes = {}) {
parentNodes = Array.isArray(parentNodes) ? parentNodes : [parentNodes];
const nodesWithMembers = parentNodes.map((parentNode) => {
const memberNodes = nodes.filter((node) => node.memberof === parentNode.longname);
return memberNodes.reduce((parentNode, node) => {
JsDoc._addChildNodes(nodes, node);
parentNode[node.group] = parentNode[node.group] || [];
parentNode[node.group].push(node);
return parentNode;
}, parentNode);
});
return (nodesWithMembers.length === 1 ? nodesWithMembers[0] : nodesWithMembers);
}
}

module.exports = JsDoc;
69 changes: 69 additions & 0 deletions lib/code-docs/jsdoc/nav.js
@@ -0,0 +1,69 @@
'use strict';

const NavNode = require('../nav-node');

/**
* Methods to build a navigation for JSDoc.
*/
class JsDocNav {

/**
* @param {JsDoc} JsDoc - Raw json doclets generated by JSDoc.
* @returns {NavNode[]} A navigation composing an array of sub-navigations e.g. [{title, items: [{title, link}]}]
*/
static createNavigation(JsDoc) {
const nav = [];
const nodes = JsDoc.getNodes();
// Add important doclets to the root of the nav with subnavs.
// E.g. add classes with funciton and proeprties as a subnav.
const jsDocByTypeWithMembers = JsDoc.getNodesByTypeWithMembers();
const docletGroups = [
{ 'name': 'classes', 'subGroups': ['functions', 'properties'] },
{ 'name': 'modules', 'subGroups': ['classes', 'functions', 'properties'] }];
docletGroups.forEach(docletGroup => {
if (jsDocByTypeWithMembers[docletGroup.name]) {
jsDocByTypeWithMembers[docletGroup.name].forEach((node) => {
nav.push(createSubNav(
`${node.name}`,
[].concat([node], ...docletGroup.subGroups.map(name => node[name]))
));
});
}
});
// Add events to nav, ignoring their hierarchy.
// I.e. Surface events which are not in the global scope.
const eventNodes = nodes.filter(node => node.kind === 'event');
if (eventNodes.length > 0) {
nav.push(createSubNav('Events', eventNodes));
}
// Add global nodes which are not already added to the nav.
const globalNodes = nodes.filter(node => node.memberof === undefined && ['class', 'module', 'events'].includes(node.kind) === false);
if (globalNodes.length > 0) {
nav.push(createSubNav('Global', globalNodes));
}
return nav;
}
}

/**
* @param {string} title - The title of the subnav.
* @param {Object[]} nodes - Formatted JS doclets to link to from the sub-navigation.
* @returns {NavNode} A sub-navigation e.g. {title, items: [{title, link}]}
* @access private
*/
function createSubNav(title, nodes) {
nodes = nodes || [];
const items = nodes.filter(node => node).map((node) => {
let title = `${node.label}: ${node.name}`;
if (node.kind === 'class' && node.memberof === undefined) {
title = 'Constructor & Overview';
}
if (node.kind === 'module' && node.memberof === undefined) {
title = 'Overview';
}
return new NavNode(title, `#${node.longname}`);
});
return new NavNode(title, items);
}

module.exports = JsDocNav;
100 changes: 100 additions & 0 deletions lib/code-docs/jsdoc/nodes/base.js
@@ -0,0 +1,100 @@
'use strict';

const Example = require('../../example');
const { URL } = require('url');

class BaseNode {
constructor(doclet) {
this.name = doclet.name;
this.longname = doclet.longname;
this.kind = doclet.kind;
this.memberof = doclet.memberof;
this.description = this.replaceLinks(doclet.description) || '';
this.access = doclet.access || '';
this.virtual = Boolean(doclet.virtual);
this.file = {
path: doclet.meta && doclet.meta.path ? doclet.meta.path : '',
name: doclet.meta && doclet.meta.filename ? doclet.meta.filename : ''
};

if (doclet.meta && isNaN(doclet.meta.lineno) === false) {
this.file.lineno = doclet.meta.lineno;
}
if (doclet.meta && isNaN(doclet.meta.columnno) === false) {
this.file.columnno = doclet.meta.columnno;
}
if (doclet.deprecated) {
this.deprecated = this.replaceLinks(doclet.deprecated);
}
}

replaceLinks(jsDocComment) {
if (typeof jsDocComment !== 'string') {
return jsDocComment;
}
return jsDocComment.replace(/(?:\[(.*)\])?{@link ([^\s|}]*)\s?(?:[|])?([^}]*)}/g, (match, p1, p2, p3) => {
//{@link namepathOrURL}
let href = p2;
let text = null;
//[link text]{@link namepathOrURL}
text = text || p1;
//{@link namepathOrURL|text}
//{@link namepathOrURL text (after the first space)}
text = text || p3;
// If no text show url/namepath.
text = text || href;
// If unable to construct url assume a JSDoc namepath and turn into a hash.
try {
new URL(href);
} catch (error) {
href = `#${href}`;
}
return `[${text}](${href})`;
});
}

addExamples(doclet, target = this) {
target.examples = target.examples || [];
if (Array.isArray(doclet.examples)) {
doclet.examples.forEach((example) => {
let code = example;
let caption;

/** @see https://github.com/jsdoc3/jsdoc/blob/832dfd704a01fc931ef54ef0a02896d89a5ee9ad/templates/default/publish.js#L470 */
if (example.match(/^\s*<caption>([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) {
caption = RegExp.$1;
code = RegExp.$3;
}

if (typeof code === 'string' && code.length > 1) {
target.examples.push(new Example(code, 'js', caption));
}
});
}
}

addParameters(doclet, target = this) {
target.parameters = target.parameters || [];
if (Array.isArray(doclet.params)) {
doclet.params.forEach((param) => target.parameters.push({
'name': param.name,
'type': param.type && param.type.names ? param.type.names : [], // param can accept multiple types
'description': param.description || '',
'default': param.hasOwnProperty('defaultvalue') ? param.defaultvalue : '',
'optional': typeof param.optional === 'boolean' ? param.optional : '',
'nullable': typeof param.nullable === 'boolean' ? param.nullable : ''
}));
}
}

addReturns(doclet, target = this) {
if (doclet.returns) {
target.returns = {
'type': doclet.returns[0] && doclet.returns[0].type && doclet.returns[0].type.names ? doclet.returns[0].type.names : [], // return can be of multiple types
'description': doclet.returns[0] && doclet.returns[0].description || ''
};
}
}
}

module.exports = BaseNode;
25 changes: 25 additions & 0 deletions lib/code-docs/jsdoc/nodes/class.js
@@ -0,0 +1,25 @@
'use strict';

const BaseNode = require('./base');

class ClassNode extends BaseNode {
constructor(doclet) {
super(doclet);
this.group = 'classes';
this.label = doclet.meta && doclet.meta.code && doclet.meta.code.type === 'ClassDeclaration' ? 'Class' : 'Constructor Function';
this.description = this.replaceLinks(doclet.classdesc) || '';
this.extends = doclet.augments || [];
this.fires = doclet.fires || [];
this.constructor = {
'name': doclet.name,
'description': this.replaceLinks(doclet.description) || '',
'parameters': [
],
'examples': []
};
this.addExamples(doclet, this.constructor);
this.addParameters(doclet, this.constructor);
}
};

module.exports = ClassNode;