Skip to content

Commit

Permalink
refactoring: move ast related code to script parser
Browse files Browse the repository at this point in the history
Moves AST specifics parsers to ScriptParser instead of using it on generic parser.
  • Loading branch information
alexprey committed Feb 13, 2021
1 parent 1a623df commit bc757a4
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 152 deletions.
6 changes: 5 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ const parseComment = (text, defaultVisibility = DEFAULT_VISIBILITY) => {
/**
* @param {Node} node
* @param {SourceCode} sourceCode
* @param {*} options
* @param {{ defaultVisibility: string, useFirst: boolean, useLeading: boolean, useTrailing: boolean }} options
* @return {import('../typings').IScopedCommentItem}
*/
const getCommentFromSourceCode = (node, sourceCode, {
defaultVisibility = DEFAULT_VISIBILITY,
Expand Down Expand Up @@ -481,6 +482,9 @@ const inferTypeFromVariableDeclaration = (variable) => {
}
};

/**
* @param {import('../typings').IScopedCommentItem} comment
*/
const isTopLevelComment = (comment) => {
return comment.keywords.some((keyword) => keyword.name === 'component');
};
Expand Down
27 changes: 27 additions & 0 deletions lib/v3/events.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
const CommonEvent = Object.freeze({
/**
* Emit the @see {SvelteDataItem} object.
*/
DATA: 'data',
/**
* Emit the @see {SvelteEventItem} object.
*/
EVENT: 'event',
/**
* Emit the global comment @see {IScopedCommentItem} object.
*/
GLOBAL_COMMENT: 'global-comment',
});

Expand All @@ -19,8 +28,26 @@ const ScriptEvent = Object.freeze({
IMPORTED_COMPONENT: 'imported-component',
});

const ParserEvent = Object.freeze({
NAME: 'name',
DESCRIPTION: 'description',
KEYWORDS: 'keywords',

DATA: 'data',
EVENT: 'event',
REF: 'ref',
SLOT: 'slot',
METHOD: 'method',
COMPUTED: 'computed',
IMPORTED_COMPONENT: 'component',

FAILURE: 'failure',
END: 'end',
});

module.exports = {
CommonEvent,
TemplateEvent,
ScriptEvent,
ParserEvent
};
167 changes: 38 additions & 129 deletions lib/v3/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,15 @@ const EventEmitter = require('events');
const path = require('path');

const utils = require('./../utils');
const jsdoc = require('./../jsdoc');
const {
normalize: normalizeOptions,
validateFeatures,
} = require('../options');

const {
parseAndMergeKeywords,
updateType
} = require('./v3-utils');

const TemplateParser = require('./template');
const { TemplateEvent, ScriptEvent } = require('./events');
const { TemplateEvent, ScriptEvent, ParserEvent } = require('./events');

const ScriptParser = require('./script');
const { SCOPE_STATIC } = require('./script');

/**
* @typedef {import('../../typings').Svelte3Feature} Svelte3Feature
Expand Down Expand Up @@ -64,7 +57,7 @@ class Parser extends EventEmitter {
try {
this.__walk();
} catch (error) {
this.emit('failure', error);
this.emit(ParserEvent.FAILURE, error);
}
});

Expand All @@ -84,7 +77,7 @@ class Parser extends EventEmitter {
this.parseTemplate(this.structure.template);
}

this.emit('end');
this.emit(ParserEvent.END);
}

static getDefaultOptions() {
Expand Down Expand Up @@ -114,112 +107,22 @@ class Parser extends EventEmitter {
}

if (this.componentName) {
this.emit('name', utils.buildCamelCase(this.componentName));
this.emit(ParserEvent.NAME, utils.buildCamelCase(this.componentName));
}
}

/**
* @sideEffect mutates `item.locations`.
*
* Attaches the node's location to the item if requested by the user.
*
* @param {*} item the item to attach locations to
* @param {{ location?: { start: number, end: number } }} node the parsed node containing a location
* @param {{ offset: number }} context parse context containing an offset for locations
* @param {import('../../typings').IScopedCommentItem} comment
*/
attachLocationsIfRequired(item, node, context) {
if (this.includeSourceLocations && node.location) {
item.locations = [{
start: node.location.start + context.offset,
end: node.location.end + context.offset,
}];
}
}

emitDataItem(variable, parseContext, defaultVisibility, parentComment) {
const comment = parentComment || utils.getCommentFromSourceCode(variable.node, parseContext.sourceCode, { defaultVisibility });

const item = Object.assign({}, comment, {
name: variable.name,
kind: variable.kind,
static: parseContext.scopeType === SCOPE_STATIC,
readonly: variable.kind === 'const',
type: utils.inferTypeFromVariableDeclaration(variable),
importPath: variable.importPath,
originalName: variable.originalName,
localName: variable.localName
});

if (variable.declarator && variable.declarator.init) {
item.defaultValue = variable.declarator.init.value;
}

this.attachLocationsIfRequired(item, variable, parseContext);

updateType(item);

this.emit('data', item);
}

emitMethodItem(method, parseContext, defaultVisibility, parentComment) {
const comment = parentComment || utils.getCommentFromSourceCode(method.node, parseContext.sourceCode, { defaultVisibility });

parseAndMergeKeywords(comment.keywords, method);

const item = Object.assign({}, comment, {
name: method.name,
params: method.params,
return: method.return,
static: parseContext.scopeType === SCOPE_STATIC
});

this.attachLocationsIfRequired(item, method, parseContext);

this.emit('method', item);
}

emitComputedItem(computed, parseContext, defaultVisibility) {
const item = Object.assign({}, utils.getCommentFromSourceCode(computed.node, parseContext.sourceCode, { defaultVisibility }), {
name: computed.name,
static: parseContext.scopeType === SCOPE_STATIC,
type: jsdoc.DEFAULT_TYPE
});

this.attachLocationsIfRequired(item, computed, parseContext);

updateType(item);

this.emit('computed', item);
}

emitEventItem(event, parseContext) {
const item = Object.assign({}, utils.getCommentFromSourceCode(event.node, parseContext.sourceCode, { defaultVisibility: 'public' }), {
name: event.name
});

this.attachLocationsIfRequired(item, event, parseContext);

this.emit('event', item);
}

emitImportedComponentItem(component, parseContext) {
const item = Object.assign({}, utils.getCommentFromSourceCode(component.node, parseContext.sourceCode, { defaultVisibility: 'private' }), {
name: component.name,
importPath: component.path,
});

this.attachLocationsIfRequired(item, component, parseContext);

this.emit('component', item);
}

emitGlobalComment(comment) {
if (comment && utils.isTopLevelComment(comment)) {
if (this.features.includes('description')) {
this.emit('description', comment.description);
this.emit(ParserEvent.DESCRIPTION, comment.description);
}

this.emit('keywords', comment.keywords);
if (this.features.includes('keywords')) {
this.emit(ParserEvent.KEYWORDS, comment.keywords);
}
}
}

Expand Down Expand Up @@ -252,7 +155,10 @@ class Parser extends EventEmitter {
return this.scriptParser;
}

this.scriptParser = new ScriptParser();
this.scriptParser = new ScriptParser({
features: this.features,
includeSourceLocations: this.includeSourceLocations
});

this.subscribeToScriptParser(this.scriptParser);

Expand All @@ -275,48 +181,51 @@ class Parser extends EventEmitter {
}

subscribeToScriptParser(scriptParser) {
scriptParser.on(ScriptEvent.COMPUTED, (computed, context, visibility, comment) => {
this.emitComputedItem(computed, context, visibility, comment);
scriptParser.on(ScriptEvent.COMPUTED, item => {
this.emit(ParserEvent.COMPUTED, item);
});
scriptParser.on(ScriptEvent.DATA, (data, context, visibility, comment) => {
this.emitDataItem(data, context, visibility, comment);
scriptParser.on(ScriptEvent.DATA, item => {
this.emit(ParserEvent.DATA, item);
});
scriptParser.on(ScriptEvent.EVENT, (event, context) => {
this.emitEventItem(event, context);
scriptParser.on(ScriptEvent.EVENT, item => {
this.emit(ParserEvent.EVENT, item);
});
scriptParser.on(ScriptEvent.GLOBAL_COMMENT, (comment) => {
this.emitGlobalComment(comment);
scriptParser.on(ScriptEvent.IMPORTED_COMPONENT, item => {
this.emit(ParserEvent.IMPORTED_COMPONENT, item);
});
scriptParser.on(ScriptEvent.IMPORTED_COMPONENT, (component, context) => {
this.emitImportedComponentItem(component, context);
scriptParser.on(ScriptEvent.METHOD, item => {
this.emit(ParserEvent.METHOD, item);
});
scriptParser.on(ScriptEvent.METHOD, (method, context, visibility, comment) => {
this.emitMethodItem(method, context, visibility, comment);

// Special cases where more parsing is required
scriptParser.on(ScriptEvent.GLOBAL_COMMENT, (comment) => {
this.emitGlobalComment(comment);
});
}

subscribeToTemplateParser(templateParser) {
// Forward emit of basic template events
templateParser.on(TemplateEvent.DATA, (dataItem) => {
this.emit('data', dataItem);
templateParser.on(TemplateEvent.DATA, dataItem => {
this.emit(ParserEvent.DATA, dataItem);
});
templateParser.on(TemplateEvent.EVENT, (event) => {
this.emit('event', event);
templateParser.on(TemplateEvent.EVENT, event => {
this.emit(ParserEvent.EVENT, event);
});
templateParser.on(TemplateEvent.NAME, (name) => {
this.emit('name', name);
templateParser.on(TemplateEvent.NAME, name => {
this.emit(ParserEvent.NAME, name);
});
templateParser.on(TemplateEvent.REF, (refItem) => {
this.emit('ref', refItem);
templateParser.on(TemplateEvent.REF, refItem => {
this.emit(ParserEvent.REF, refItem);
});
templateParser.on(TemplateEvent.SLOT, (slot) => {
this.emit('slot', slot);
templateParser.on(TemplateEvent.SLOT, slot => {
this.emit(ParserEvent.SLOT, slot);
});

// Special cases where more parsing is required
templateParser.on(TemplateEvent.EXPRESSION, (expression) => {
this.parseTemplateJavascriptExpression(expression);
});

templateParser.on(TemplateEvent.GLOBAL_COMMENT, (comment) => {
this.emitGlobalComment(comment);
});
Expand Down
Loading

0 comments on commit bc757a4

Please sign in to comment.