sveltedoc-parser
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -6,2 +6,10 @@ # Change Log | ||
## [2.1.0] 09.08.2019 | ||
- [Added] Svelte V3: Implement support for property binding parsing (`bind:proprty={...}`) | ||
- [Added] Svelte V3: Implement support for event parsing which dispatched from code (`dispatch(...)`) | ||
- [Added] Svelte V3: Implement support for event parsing which dispatched from markup expressions (`<button on:click="{() => dispatch(....)}">`) | ||
- [Added] Svelte V3: Implement support for ref parsing (`bind:this={...}`) | ||
- [Fixed] Spec: Property `SvelteDataItem.let` changed to `SvelteDateItem.kind`, that was named as `let` by mistake | ||
## [2.0.0] 05.08.2019 | ||
@@ -8,0 +16,0 @@ |
41
index.js
@@ -64,2 +64,35 @@ const { loadFileStructureFromOptions } = require('./lib/helpers'); | ||
function convertVisibilityToLevel(visibility) { | ||
switch (visibility) { | ||
case 'public': | ||
return 3; | ||
case 'protected': | ||
return 2; | ||
case 'private': | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
function mergeItems(currentItem, newItem) { | ||
if (convertVisibilityToLevel(currentItem.visibility) < convertVisibilityToLevel(newItem.visibility)) { | ||
currentItem.visibility = newItem.visibility; | ||
} | ||
if (!currentItem.description && newItem.description) { | ||
currentItem.description = newItem; | ||
} | ||
if (!currentItem.keywords && newItem.keywords) { | ||
currentItem.keywords = newItem.keywords; | ||
} | ||
if (!currentItem.bind && newItem.bind) { | ||
currentItem.bind = newItem.bind; | ||
} | ||
return currentItem; | ||
} | ||
function subscribeOnParserEvents(parser, options, version, resolve, reject) { | ||
@@ -94,3 +127,9 @@ const component = { | ||
} else { | ||
component[feature][itemIndex] = value; | ||
// Use merge logic of items information for specific features | ||
if (['data'].includes(feature)) { | ||
const currentItem = component[feature][itemIndex]; | ||
component[feature][itemIndex] = mergeItems(currentItem, value); | ||
} else { | ||
component[feature][itemIndex] = value; | ||
} | ||
} | ||
@@ -97,0 +136,0 @@ }); |
@@ -70,13 +70,15 @@ const RE_VISIBILITY = /(public|protected|private)/; | ||
const getComment = (property, defaultVisibility = DEFAULT_VISIBILITY, features) => { | ||
const getComment = (property, defaultVisibility = DEFAULT_VISIBILITY, features, useLeading = true, useTrailing = true) => { | ||
let lastComment = null; | ||
if (property.leadingComments) { | ||
lastComment = property.leadingComments.pop().value; | ||
if (property) { | ||
if (useLeading && property.leadingComments && property.leadingComments.length > 0) { | ||
lastComment = property.leadingComments[property.leadingComments.length - 1].value; | ||
} | ||
if (useTrailing && property.trailingComments && property.trailingComments.length > 0) { | ||
lastComment = property.trailingComments[property.trailingComments.length - 1].value; | ||
} | ||
} | ||
if (property.trailingComments) { | ||
lastComment = property.trailingComments.pop().value; | ||
} | ||
if (lastComment) { | ||
@@ -83,0 +85,0 @@ return parseComment(lastComment, defaultVisibility, features); |
@@ -17,12 +17,13 @@ const EventEmitter = require('events'); | ||
'methods', | ||
// 'actions', | ||
// 'helpers', | ||
'components', | ||
// 'description', | ||
'description', | ||
'events', | ||
'slots', | ||
// 'transitions', | ||
// 'store' | ||
'refs' | ||
]; | ||
const SCOPE_DEFAULT = 'default'; | ||
const SCOPE_STATIC = 'static'; | ||
const SCOPE_MARKUP = 'markup'; | ||
class Parser extends EventEmitter { | ||
@@ -44,2 +45,5 @@ constructor(structure, options) { | ||
this.imports = {}; | ||
this.dispatcherConstructorNames = []; | ||
this.dispatcherNames = []; | ||
} | ||
@@ -105,7 +109,9 @@ | ||
emitDataItem(variable, isStaticScope, defaultVisibility) { | ||
const item = Object.assign({}, utils.getComment(variable.node, defaultVisibility), { | ||
emitDataItem(variable, scopeType, defaultVisibility, parentComment) { | ||
const comment = parentComment || utils.getComment(variable.node, defaultVisibility); | ||
const item = Object.assign({}, comment, { | ||
name: variable.name, | ||
kind: variable.kind, | ||
static: isStaticScope, | ||
static: scopeType === SCOPE_STATIC, | ||
readonly: variable.kind === 'const', | ||
@@ -120,7 +126,9 @@ type: jsdoc.DEFAULT_TYPE | ||
emitMethodItem(method, isStaticScope, defaultVisibility) { | ||
const item = Object.assign({}, utils.getComment(method.node, defaultVisibility), { | ||
emitMethodItem(method, scopeType, defaultVisibility, parentComment) { | ||
const comment = parentComment || utils.getComment(method.node, defaultVisibility); | ||
const item = Object.assign({}, comment, { | ||
name: method.name, | ||
args: method.args, | ||
static: isStaticScope | ||
static: scopeType === SCOPE_STATIC | ||
}); | ||
@@ -131,6 +139,6 @@ | ||
emitComputedItem(computed, isStaticScope, defaultVisibility) { | ||
emitComputedItem(computed, scopeType, defaultVisibility) { | ||
const item = Object.assign({}, utils.getComment(computed.node, defaultVisibility), { | ||
name: computed.name, | ||
static: isStaticScope, | ||
static: scopeType === SCOPE_STATIC, | ||
type: jsdoc.DEFAULT_TYPE | ||
@@ -144,2 +152,18 @@ }); | ||
emitEventItem(event) { | ||
const item = Object.assign({}, utils.getComment(event.node, 'public'), { | ||
name: event.name | ||
}); | ||
this.emit('event', item); | ||
} | ||
emitRefItem(ref) { | ||
const item = Object.assign({}, ref, { | ||
visibility: 'private' | ||
}); | ||
this.emit('ref', item); | ||
} | ||
emitImportedComponentItem(importNode, name, path) { | ||
@@ -154,20 +178,58 @@ const item = Object.assign({}, utils.getComment(importNode, 'private'), { | ||
parseScriptBlock(scriptBlock) { | ||
const ast = espree.parse(scriptBlock.content, { | ||
attachComment: true, | ||
tokens: true, | ||
ecmaVersion: 9, | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
experimentalObjectRestSpread: true | ||
parseBodyRecursively(rootNode, scopeType, level) { | ||
const nodes = rootNode.body | ||
? rootNode.body | ||
: (rootNode.length > 0 ? rootNode : [rootNode]); | ||
nodes.forEach(node => { | ||
if (node.type === 'ExpressionStatement') { | ||
const expressionNode = node.expression; | ||
if (expressionNode.type === 'CallExpression') { | ||
const callee = expressionNode.callee; | ||
if (callee.type === 'Identifier' && this.dispatcherNames.indexOf(callee.name) >= 0) { | ||
const eventItem = this.parseEvenDeclaration(expressionNode); | ||
this.emitEventItem(eventItem); | ||
return; | ||
} | ||
} | ||
if (expressionNode.type === 'ArrowFunctionExpression') { | ||
if (expressionNode.body) { | ||
this.parseBodyRecursively(expressionNode.body, scopeType, level + 1); | ||
return; | ||
} | ||
} | ||
} | ||
}); | ||
const isStaticScope = /\sscope=('module'|"module")/gi.test(scriptBlock.attributes) | ||
if (node.type === 'CallExpression') { | ||
const callee = node.callee; | ||
if (callee.type === 'Identifier' && this.dispatcherNames.indexOf(callee.name) >= 0) { | ||
const eventItem = this.parseEvenDeclaration(node); | ||
this.emitEventItem(eventItem); | ||
return; | ||
} | ||
} | ||
ast.body.forEach(node => { | ||
if (node.type === 'VariableDeclaration') { | ||
if (node.type === 'VariableDeclaration' && scopeType !== SCOPE_MARKUP) { | ||
const variables = this.parseVariableDeclaration(node); | ||
variables.forEach(variable => { | ||
this.emitDataItem(variable, isStaticScope, 'private'); | ||
if (level === 0) { | ||
this.emitDataItem(variable, scopeType, 'private'); | ||
} | ||
if (variable.declarator.init) { | ||
const initNode = variable.declarator.init; | ||
if (initNode.type === 'CallExpression') { | ||
const callee = initNode.callee; | ||
if (callee.type === 'Identifier' && this.dispatcherConstructorNames.indexOf(callee.name) >= 0) { | ||
this.dispatcherNames.push(variable.name); | ||
} | ||
} else if (initNode.type === 'ArrowFunctionExpression') { | ||
if (initNode.body) { | ||
this.parseBodyRecursively(initNode.body, scopeType, level + 1); | ||
} | ||
} | ||
} | ||
}); | ||
@@ -179,14 +241,19 @@ | ||
if (node.type === 'FunctionDeclaration') { | ||
this.emitMethodItem(this.parseFunctionDeclaration(node), isStaticScope, 'private'); | ||
this.emitMethodItem(this.parseFunctionDeclaration(node), scopeType, 'private'); | ||
if (node.body) { | ||
this.parseBodyRecursively(node.body, scopeType, level + 1); | ||
} | ||
return; | ||
} | ||
if (node.type === 'ExportNamedDeclaration') { | ||
if (node.type === 'ExportNamedDeclaration' && level === 0 && scopeType !== SCOPE_MARKUP) { | ||
const declaration = node.declaration; | ||
if (declaration) { | ||
const exportNodeComment = utils.getComment(node, 'public', this.features, true, false); | ||
if (declaration.type === 'VariableDeclaration') { | ||
const variables = this.parseVariableDeclaration(declaration); | ||
variables.forEach(variable => { | ||
this.emitDataItem(variable, isStaticScope, 'public'); | ||
this.emitDataItem(variable, scopeType, 'public', exportNodeComment); | ||
}); | ||
@@ -198,3 +265,5 @@ | ||
if (declaration.type === 'FunctionDeclaration') { | ||
this.emitMethodItem(this.parseFunctionDeclaration(declaration), isStaticScope, 'public'); | ||
const func = this.parseFunctionDeclaration(declaration); | ||
this.emitMethodItem(func, scopeType, 'public', exportNodeComment); | ||
return; | ||
@@ -205,3 +274,3 @@ } | ||
if (node.type === 'LabeledStatement') { | ||
if (node.type === 'LabeledStatement' && level === 0 && scopeType !== SCOPE_MARKUP) { | ||
const idNode = node.label; | ||
@@ -217,3 +286,3 @@ if (idNode && idNode.type === 'Identifier' && idNode.name === '$') { | ||
node: node | ||
}, isStaticScope, 'private'); | ||
}, scopeType, 'private'); | ||
@@ -227,3 +296,3 @@ return; | ||
if (node.type === 'ImportDeclaration') { | ||
if (node.type === 'ImportDeclaration' && level === 0 && scopeType !== SCOPE_MARKUP) { | ||
const specifier = node.specifiers[0]; | ||
@@ -252,3 +321,3 @@ | ||
kind: 'const' | ||
}, isStaticScope, 'private'); | ||
}, scopeType, 'private'); | ||
} | ||
@@ -265,12 +334,84 @@ } | ||
kind: 'const' | ||
}, isStaticScope, 'private'); | ||
}, scopeType, 'private'); | ||
} | ||
}); | ||
// Import svelte API functions | ||
if (node.source.type === 'Literal' && node.source.value === 'svelte') { | ||
// Dispatcher constructors | ||
node.specifiers | ||
.filter(specifier => specifier.imported.name === 'createEventDispatcher') | ||
.forEach(specifier => { | ||
this.dispatcherConstructorNames.push(specifier.local.name); | ||
}); | ||
} | ||
} | ||
} | ||
//console.log(util.inspect(node, false, null, true)); | ||
// console.log(util.inspect(node, false, null, true)); | ||
if (node.body) { | ||
this.parseBodyRecursively(node.body, scopeType, level + 1); | ||
} | ||
}); | ||
} | ||
getAstParsingOptions() { | ||
return { | ||
attachComment: true, | ||
tokens: true, | ||
ecmaVersion: 9, | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
experimentalObjectRestSpread: true | ||
} | ||
}; | ||
} | ||
parseScriptBlock(scriptBlock) { | ||
const ast = espree.parse( | ||
scriptBlock.content, | ||
this.getAstParsingOptions() | ||
); | ||
const isStaticScope = /\sscope=('module'|"module")/gi.test(scriptBlock.attributes) | ||
this.parseBodyRecursively( | ||
ast, | ||
isStaticScope | ||
? SCOPE_STATIC | ||
: SCOPE_DEFAULT, | ||
0 | ||
); | ||
} | ||
parseMarkupExpressionBlock(expression) { | ||
const ast = espree.parse( | ||
expression, | ||
this.getAstParsingOptions() | ||
); | ||
this.parseBodyRecursively(ast, SCOPE_MARKUP, 0); | ||
} | ||
parseEvenDeclaration(node) { | ||
if (node.type !== 'CallExpression') { | ||
throw new Error('Node should have a CallExpressionType, but is ' + node.type); | ||
} | ||
const args = node.arguments; | ||
if (!args && args.length < 1) { | ||
return null; | ||
} | ||
const nameNode = args[0]; | ||
return { | ||
name: nameNode.type === 'Literal' | ||
? nameNode.value | ||
: undefined, | ||
node: node | ||
}; | ||
} | ||
parseVariableDeclaration(node) { | ||
@@ -289,3 +430,4 @@ if (node.type !== 'VariableDeclaration') { | ||
kind: node.kind, | ||
node: node | ||
node: node, | ||
declarator: declarator | ||
}); | ||
@@ -299,3 +441,4 @@ } else if (idNode.type === 'ObjectPattern') { | ||
kind: node.kind, | ||
node: node | ||
node: node, | ||
declarator: declarator | ||
}); | ||
@@ -444,2 +587,66 @@ } | ||
} | ||
if (this.features.includes('data')) { | ||
const bindProperties = Object.keys(attrs) | ||
.filter(name => name.length > 5 && name.indexOf('bind:') === 0) | ||
.filter(name => name !== 'bind:this') | ||
.map(name => { | ||
const sourcePropertyName = name.substr(5); | ||
let targetPropertyName = sourcePropertyName; | ||
const attributeValue = attrs[name]; | ||
if (attributeValue && attributeValue.length > 2 && attributeValue.charAt(0) === '{' && attributeValue.charAt(attributeValue.length - 1) === '}') { | ||
targetPropertyName = attributeValue.substr(1, attributeValue.length - 2); | ||
} | ||
return { | ||
sourcePropertyName: sourcePropertyName, | ||
targetPropertyName: targetPropertyName, | ||
parent: tagName, | ||
loc: this.includeSourceLocations && lastAttributeLocations.hasOwnProperty(name) | ||
? lastAttributeLocations[name] | ||
: null | ||
}; | ||
}); | ||
bindProperties.forEach(bindProperty => { | ||
const dataItem = { | ||
name: bindProperty.targetPropertyName, | ||
kind: 'let', | ||
bind: { | ||
source: bindProperty.parent, | ||
property: bindProperty.sourcePropertyName | ||
}, | ||
loc: bindProperty.loc, | ||
visibility: 'private', | ||
static: false, | ||
readonly: false | ||
}; | ||
this.emit('data', dataItem); | ||
}); | ||
} | ||
if (this.features.includes('refs')) { | ||
if (attrs.hasOwnProperty('bind:this') && attrs['bind:this']) { | ||
const value = attrs['bind:this']; | ||
if (value.length > 2 && value.charAt(0) === '{' && value.charAt(value.length - 1) === '}') { | ||
const bindedVariableName = value.substr(1, value.length - 2); | ||
this.emitRefItem({ | ||
name: bindedVariableName, | ||
parent: tagName, | ||
loc: this.includeSourceLocations && lastAttributeLocations.hasOwnProperty('bind:this') | ||
? lastAttributeLocations[name] | ||
: null | ||
}); | ||
} | ||
} | ||
} | ||
// Parse event handlers | ||
Object.keys(attrs) | ||
.filter(name => name.length > 3 && name.indexOf('on:') === 0 && attrs[name]) | ||
.forEach(name => { | ||
this.parseMarkupExpressionBlock(attrs[name]); | ||
}); | ||
} | ||
@@ -446,0 +653,0 @@ } |
{ | ||
"name": "sveltedoc-parser", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "Generate a JSON documentation for a Svelte file", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -27,6 +27,11 @@ # The sveltedoc parser | ||
- Extract list of references that attached to components or HTML elements | ||
- Extract all fired events | ||
- Extract information about events | ||
- Events that propogated from child component or HTML elements `<button on:click>...</button>` | ||
- Parse event modificators `... on:click|once` | ||
- Extract all fired events (_Svelte 2 only_) | ||
- Events that fired by this component by `fire(...)` method | ||
- Events that propogated from child component or HTML elements | ||
- Custom event handlers with `private` visibility scope | ||
- Extract all dispatched events (_Svelte 3_) | ||
- Events that dispatched from script block by user-created dispatcher | ||
- Events that dispatched from markup expressions by user-created dispatcher | ||
- Extract list of used default and named `slots` | ||
@@ -39,5 +44,5 @@ - Extract component methods | ||
- Identify default values for optional parameters (`@param [parameter=Default value]`) | ||
- Extract component helpers | ||
- Extract component actions | ||
- Extract component transitions | ||
- Extract component helpers (_Svelte 2 only_) | ||
- Extract component actions (_Svelte 2 only_) | ||
- Extract component transitions (_Svelte 2 only_) | ||
- Extract source locations for component symbols | ||
@@ -60,3 +65,3 @@ - data | ||
| **includeSourceLocations** | Flag, which indicates that source locations should be provided for component symbols. | `false` | | ||
| **version** | Optional. Use 2 or 3 to specify which svelte syntax should be used. | `undefined` | | ||
| **version** | Optional. Use `2` or `3` to specify which svelte syntax should be used. When that is not provided, parser try to detect version of the syntax. | `undefined` | | ||
| **defaultVersion** | Optional. Specify default version of svelte syntax, if auto-detector can't indetify correct version. | `undefined` | | ||
@@ -66,14 +71,14 @@ | ||
- `'name'` - Extract the component name. | ||
- `'data'` - Extract and parse the list of component data properties. | ||
- `'computed'` - Extract and parse the list of component computed properties. | ||
- `'methods'` - Extract the list of component methods. | ||
- `'actions'` - Extract the list of component actions. | ||
- `'helpers'` - Extract the list of component helpers. | ||
- `'components'` - Extract the list of imported components. | ||
- `'description'` - Extract the component description. | ||
- `'events'` - Extract the list of events that fired by this component. | ||
- `'slots'` - Extract the list of slots provided by this component. | ||
- `'transitions'` - Extract the list of transitions used by this component. | ||
- `'refs'` - Extract the list of references used by this component. | ||
- `'name'` - Extract the component name (_Supported by Svelte 2 and Svelte 3_). | ||
- `'data'` - Extract and parse the list of component data properties (_Supported by Svelte 2 and Svelte 3_). | ||
- `'computed'` - Extract and parse the list of component computed properties (_Supported by Svelte 2 and Svelte 3_). | ||
- `'methods'` - Extract the list of component methods (_Supported by Svelte 2 and Svelte 3_). | ||
- `'actions'` - Extract the list of component actions (_Supported by Svelte 2_). | ||
- `'helpers'` - Extract the list of component helpers (_Supported by Svelte 2_). | ||
- `'components'` - Extract the list of imported components (_Supported by Svelte 2 and Svelte 3_). | ||
- `'description'` - Extract the component description (_Supported by Svelte 2 and Svelte 3_). | ||
- `'events'` - Extract the list of events that fired by this component (_Supported by Svelte 2 and Svelte 3_). | ||
- `'slots'` - Extract the list of slots provided by this component (_Supported by Svelte 2 and Svelte 3_). | ||
- `'transitions'` - Extract the list of transitions used by this component (_Supported by Svelte 2_). | ||
- `'refs'` - Extract the list of references used by this component (_Supported by Svelte 2 and Svelte 3_). | ||
@@ -84,3 +89,3 @@ ## Output format | ||
See example of output [here](/test/overall/overall.main.doc.json) presented in JSON format for [this component](/test/overall/main.svelte). | ||
See example of output for Svelte 2 component [here](/test/svelte2/integration/overall/overall.main.doc.json) presented in JSON format for [this component](/test/svelte2/integration/overall/main.svelte). | ||
@@ -87,0 +92,0 @@ ## Usage |
@@ -79,2 +79,14 @@ /** | ||
export interface SvelteDataBindMapping { | ||
/** | ||
* The parent component name or DOM element from which are was binded. | ||
*/ | ||
source: string; | ||
/** | ||
* The name of the property which are was binded. | ||
*/ | ||
property: string; | ||
} | ||
export interface SvelteDataItem extends ISvelteItem { | ||
@@ -90,4 +102,10 @@ /** | ||
*/ | ||
let?: 'var'|'let'|'const'; | ||
kind?: 'var'|'let'|'const'; | ||
/** | ||
* Provides information about property binding. | ||
* @since Svelte V3 | ||
* @since {2.1.0} | ||
*/ | ||
bind?: SvelteDataBindMapping; | ||
/** | ||
* Indicates that this data item of component located in static context. | ||
@@ -271,2 +289,9 @@ * Variable should be declared in `<script scope="module" />` block. | ||
transitions?: SvelteMethodItem[]; | ||
/** | ||
* The list of event dispatchers that was created in this component. | ||
* @since Svelte V3 | ||
* @since {2.1.0} | ||
*/ | ||
dispatchers?: SvelteMethodItem[]; | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
107090
2074
129