Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
alexprey committed Oct 30, 2018
0 parents commit 6df622e
Show file tree
Hide file tree
Showing 18 changed files with 1,682 additions and 0 deletions.
69 changes: 69 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"extends": "standard",
"parserOptions": {
"ecmaFeatures": {
"jsx": false
}
},
"env": {
"node": true,
"mocha": true
},
"globals": {
"expect": false
},
"plugins": [
"chai-expect"
],
"settings": {
},
"rules": {
"no-var": "error",
"indent": ["error", 4, { "SwitchCase": 1 }],
"semi": ["error", "always"],
"object-curly-spacing": ["error", "always"],
"comma-dangle": ["error", "only-multiline"],
"space-before-function-paren": [
"error",
{
"anonymous": "always",
"named": "never",
"asyncArrow": "always"
}
],
"padding-line-between-statements": [
"error",
{
"blankLine": "always",
"prev": ["directive", "import", "const", "let", "block-like"],
"next": "*"
},
{
"blankLine": "always",
"prev": "*",
"next": ["export", "return", "throw", "block-like"]
},
{
"blankLine": "any",
"prev": "import",
"next": "import"
},
{
"blankLine": "any",
"prev": "const",
"next": "const"
},
{
"blankLine": "any",
"prev": "let",
"next": "let"
}
],
"padded-blocks": ["error", "never"],
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-const": "error",
"chai-expect/terminating-properties": 1,
"no-unused-expressions": "off"
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/node_modules
*.log
/build
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# The sveltedoc parser

## Key Features

- JSDoc support
- Extract the component description from JSDoc
- Extract used components list (with short or reference notations)
- Extract data properties
- Extract computed properties with dependencies
- Extract events that fired by this component
- Extract events that propogated from child component
- Extract custom implemented events
- Extract list of used default and named `slots`
- Extract component methods
- Extract component helpers
- Extract component actions
- Extract used `refs` in template nodes

## Configuration

| json Path | Description | Default value |
|---------|-----------|---------------|
| **filename** | The filename to parse. **Required**, unless `fileContent` is passed. | |
| **fileContent** | The file content to parse. **Required**, unless `filename` is passed. | |
| **encoding** | The file encoding. | 'utf8' |
| **features** | The component features to parse and extracting. | By default used all supported features. |
| **ignoredVisibilities** | The list of ignored visibilities. | `['private', 'protected']` |

## Issues

All list of known issues presented at [this page](https://github.com/alexprey/sveltedoc-parser/issues).
Found a new issues? Please contribute and write detailed description [here](https://github.com/alexprey/sveltedoc-parser/issues/new).

## Contributors

Author [Alexey Mulyukin](https://github.com/alexprey)

Based on [vuedoc-parse](https://gitlab.com/vuedoc/parser)
121 changes: 121 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
const fs = require('fs');
const path = require('path');

const Parser = require('./lib/parser');

const DEFAULT_ENCODING = 'utf8';
const DEFAULT_IGNORED_VISIBILITIES = ['protected', 'private'];

function parseOptions(options) {
if (!options || (!options.filename && !options.fileContent)) {
throw new Error('One of options.filename or options.filecontent is required');
}

options.encoding = options.encoding || DEFAULT_ENCODING;
options.ignoredVisibilities = options.ignoredVisibilities || DEFAULT_IGNORED_VISIBILITIES;
}

module.exports.parse = (options) => new Promise((resolve) => {
parseOptions(options);

if (!options.source) {
if (options.filename) {
if (path.extname(options.filename) === '.js') {
options.source = {
template: '',
script: fs.readFileSync(options.filename, options.encoding)
};
} else {
options.source = loadSourceFromFileContent(
fs.readFileSync(options.filename, options.encoding));
}
} else {
options.source = loadSourceFromFileContent(options.fileContent);
}
}

const component = {};
const parser = new Parser(options);

parser.features.forEach((feature) => {
switch (feature) {
case 'name':
case 'description':
component[feature] = null;
parser.on(feature, (value) => (component[feature] = value));
break;

case 'keywords':
component[feature] = [];
parser.on(feature, (value) => (component[feature] = value));
break;

default:
component[feature] = [];

const eventName = Parser.getEventName(feature);

parser.on(eventName, (value) => {
const itemIndex = component[feature].findIndex(item => item.name === value.name);

if (itemIndex < 0) {
component[feature].push(value);
} else {
component[feature][itemIndex] = value;
}
});
}
});

parser.on('end', () => {
parser.features.forEach((feature) => {
if (component[feature] instanceof Array) {
component[feature] = component[feature].filter((item) => {
return !options.ignoredVisibilities.includes(item.visibility);
});
}
});

resolve(component);
});

parser.walk();
});

function extractContentFromHtmlBlock(content, blockName) {
let leftContent = content;
let innerBlockContent = '';
let attributes = '';

const blockStart = leftContent.indexOf(`<${blockName}`);

if (blockStart >= 0) {
const blockEnd = leftContent.indexOf(`</${blockName}>`, blockStart + blockName.length + 1);

if (blockEnd >= 0) {
const openTagEndIndex = leftContent.indexOf('>', blockStart + blockName.length);

attributes = leftContent.substr(blockStart + blockName.length + 1, openTagEndIndex - blockStart - blockName.length - 1);
innerBlockContent = leftContent.substr(openTagEndIndex + 1, blockEnd - openTagEndIndex - 1);

leftContent = leftContent.substr(0, blockStart) + leftContent.substr(blockEnd + blockName.length + 3);
}
}

return {
leftContent: leftContent,
innerContent: innerBlockContent,
attributes: attributes
};
}

function loadSourceFromFileContent(fileContent) {
const script = extractContentFromHtmlBlock(fileContent, 'script');
const style = extractContentFromHtmlBlock(script.leftContent, 'style');

return {
template: style.leftContent,
script: script.innerContent,
style: style.innerContent
};
}
62 changes: 62 additions & 0 deletions lib/jsdoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const PARAM_NAME = '[a-z0-9$\\.\\[\\]_]+';
const TYPE = '[a-z\\[\\]|\\. \\*]*';
const PARAM_RE = new RegExp(`^\\s*(\\{\\(?(${TYPE})\\)?\\}\\s+)?(${PARAM_NAME}|\\[(${PARAM_NAME})(=(.*))?\\])\\s+-?\\s*(.*)`, 'i');
const RETURN_RE = new RegExp(`^\\s*(\\{\\(?(${TYPE})\\)?\\}\\s+)?-?(.*)`, 'i');
const DEFAULT_TYPE = 'Any';

function parseType(type, param) {
if (type.indexOf('|') > -1) {
param.type = type.split('|');
} else if (type.startsWith('...')) {
param.type = type.substring(3);
param.repeated = true;
} else if (type === '*') {
param.type = DEFAULT_TYPE;
} else {
param.type = type;
}
}

function parseParamKeyword(text) {
const param = { type: DEFAULT_TYPE, name: null, desc: null };
const matches = PARAM_RE.exec(text);

if (matches) {
if (matches[2]) {
parseType(matches[2], param);
}

if (matches[3][0] === '[') {
param.optional = true;
param.name = matches[4] || matches[3].substring(1, matches[3].length - 1);

if (matches[6]) {
param.default = matches[6];
}
} else {
param.name = matches[3];
}

param.desc = matches[7];
}

return param;
}

function parseReturnKeyword(text) {
const output = { type: DEFAULT_TYPE, desc: '' };
const matches = RETURN_RE.exec(text);

if (matches[2]) {
parseType(matches[2], output);
}

output.desc = matches[3];

return output;
}

module.exports.parseType = parseType;
module.exports.parseParamKeyword = parseParamKeyword;
module.exports.parseReturnKeyword = parseReturnKeyword;
module.exports.DEFAULT_TYPE = DEFAULT_TYPE;
Loading

0 comments on commit 6df622e

Please sign in to comment.