Skip to content

Commit

Permalink
feat(options-v3): Improve options validation for svelte v3 parser (Ka…
Browse files Browse the repository at this point in the history
…tChaotic#29)

- refactor: move features validation to lib/options
- refactor: move ast options to lib/options
- test: Add tests for options.validateFeatures
- test: Add integration test for features validation
  • Loading branch information
soft-decay committed Dec 7, 2020
1 parent 5e2ef37 commit 52b6371
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 126 deletions.
112 changes: 100 additions & 12 deletions lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function getUnsupportedVisibilitiesString(arr) {
INFO_VISIBILITIES_SUPPORTED;
}

const ErrorMessage = Object.freeze({
const OptionsError = Object.freeze({
OptionsRequired: 'An options object is required.',
InputRequired: 'One of options.filename or options.fileContent is required.',
EncodingMissing: 'Internal Error: options.encoding is not set.',
Expand Down Expand Up @@ -94,7 +94,7 @@ function normalize(options) {
*/
function validate(options) {
if (!options) {
throw new Error(ErrorMessage.OptionsRequired);
throw new Error(OptionsError.OptionsRequired);
}

normalize(options);
Expand All @@ -110,42 +110,130 @@ function validate(options) {
isString(options.fileContent);

if (!hasFilename && !hasFileContent) {
throw new Error(ErrorMessage.InputRequired);
throw new Error(OptionsError.InputRequired);
}

if ('encoding' in options) {
if (!isString(options.encoding)) {
throw new Error(ErrorMessage.EncodingFormat);
throw new Error(OptionsError.EncodingFormat);
}

if (!ENCODINGS.includes(options.encoding)) {
throw new Error(ErrorMessage.EncodingNotSupported(options.encoding));
throw new Error(OptionsError.EncodingNotSupported(options.encoding));
}
} else {
// Sanity check. At this point, 'encoding' should be set.
throw new Error(ErrorMessage.EncodingMissing);
throw new Error(OptionsError.EncodingMissing);
}

if ('ignoredVisibilities' in options) {
if (!Array.isArray(options.ignoredVisibilities)) {
throw new Error(ErrorMessage.IgnoredVisibilitiesFormat);
throw new Error(OptionsError.IgnoredVisibilitiesFormat);
}

if (!options.ignoredVisibilities.every(isVisibilitySupported)) {
const notSupported = options.ignoredVisibilities.filter(
(iv) => !isVisibilitySupported(iv)
);

throw new Error(ErrorMessage.IgnoredVisibilitiesNotSupported(notSupported));
throw new Error(OptionsError.IgnoredVisibilitiesNotSupported(notSupported));
}
} else {
// Sanity check. At this point, 'ignoredVisibilities' should be set.
throw new Error(ErrorMessage.IgnoredVisibilitiesMissing);
throw new Error(OptionsError.IgnoredVisibilitiesMissing);
}
}

const getSupportedFeaturesString = (supported) => `Supported features: ${printArray(supported)}`;

const getFeaturesEmptyString = (supported) => {
return 'options.features must contain at least one feature. ' +
getSupportedFeaturesString(supported);
};

/**
* @param {string[]} notSupported
* @param {string[]} supported
*/
const getFeaturesNotSupportedString = (notSupported, supported) => {
return `Features [${printArray(notSupported)}] in ` +
'options.features are not supported by this Parser. ' +
getSupportedFeaturesString(supported);
};

const ParserError = {
FeaturesMissing: 'Internal Error: options.features is not set.',
FeaturesFormat: 'options.features must be an array',
FeaturesEmpty: getFeaturesEmptyString,
FeaturesNotSupported: getFeaturesNotSupportedString,
};

/**
*
* @param {*} options
* @param {string[]} supported
* @throws if any validation fails for options.features
*/
function validateFeatures(options, supported) {
if ('features' in options) {
if (!Array.isArray(options.features)) {
throw new TypeError(ParserError.FeaturesFormat);
}

if (options.features.length === 0) {
throw new Error(ParserError.FeaturesEmpty(supported));
}

const notSupported = options.features.filter((iv) => !supported.includes(iv));

if (notSupported.length > 0) {
throw new Error(ParserError.FeaturesNotSupported(notSupported, supported));
}
} else {
throw new Error(ParserError.FeaturesMissing);
}
}

/**
* @link https://github.com/eslint/espree#options
*/
function getAstDefaultOptions() {
return {
/** attach range information to each node */
range: true,

/** attach line/column location information to each node */
loc: true,

/** create a top-level comments array containing all comments */
comment: true,

/** create a top-level tokens array containing all tokens */
tokens: true,

/**
* Set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify
* the version of ECMAScript syntax you want to use.
*
* You can also set to 2015 (same as 6), 2016 (same as 7),
* 2017 (same as 8), 2018 (same as 9), 2019 (same as 10),
* 2020 (same as 11), or 2021 (same as 12) to use the year-based naming.
*/
ecmaVersion: 9,

/** specify which type of script you're parsing ("script" or "module") */
sourceType: 'module',

/** specify additional language features */
ecmaFeatures: {}
};
}

module.exports = {
ErrorMessage: ErrorMessage,
validate: validate,
retrieveFileOptions: retrieveFileOptions,
OptionsError,
ParserError,
validate,
validateFeatures,
retrieveFileOptions,
getAstDefaultOptions,
};
90 changes: 14 additions & 76 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,10 @@ const HtmlParser = require('htmlparser2-svelte').Parser;
const path = require('path');
const utils = require('./utils');
const jsdoc = require('./jsdoc');
const { validateFeatures, getAstDefaultOptions } = require('./options');

const hasOwnProperty = utils.hasOwnProperty;

/**
* @link https://github.com/eslint/espree#options
*/
function getAstDefaultOptions() {
return {
/** attach range information to each node */
range: true,

/** attach line/column location information to each node */
loc: true,

/** create a top-level comments array containing all comments */
comment: true,

/** create a top-level tokens array containing all tokens */
tokens: true,

/**
* Set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify
* the version of ECMAScript syntax you want to use.
*
* You can also set to 2015 (same as 6), 2016 (same as 7),
* 2017 (same as 8), 2018 (same as 9), 2019 (same as 10),
* 2020 (same as 11), or 2021 (same as 12) to use the year-based naming.
*/
ecmaVersion: 9,

/** specify which type of script you're parsing ("script" or "module") */
sourceType: 'module',

/** specify additional language features */
ecmaFeatures: {}
};
}

const SUPPORTED_FEATURES = [
'name',
'data',
Expand All @@ -59,15 +25,6 @@ const SUPPORTED_FEATURES = [
'refs',
'store'
];
const isFeatureSupported = (v) => SUPPORTED_FEATURES.includes(v);

const INFO_FEATURES_SUPPORTED = `Supported features: ${JSON.stringify(SUPPORTED_FEATURES)}`;

function getUnsupporteFeaturesString(arr) {
return `Features [${utils.printArray(arr)}] in ` +
'options.features are not supported by the SvelteV2 Parser. ' +
INFO_FEATURES_SUPPORTED;
}

const EVENT_EMIT_RE = /\bfire\s*\(\s*((?:'[^']*')|(?:"[^"]*")|(?:`[^`]*`))/;

Expand Down Expand Up @@ -117,7 +74,7 @@ class Parser extends EventEmitter {
this.componentName = options.componentName;
this.template = options.source.template;
this.filename = options.filename;
this.eventsEmmited = options.eventsEmmited;
this.eventsEmitted = options.eventsEmitted;
this.defaultMethodVisibility = options.defaultMethodVisibility;
this.defaultActionVisibility = options.defaultActionVisibility;
this.identifiers = options.identifiers;
Expand All @@ -131,7 +88,7 @@ class Parser extends EventEmitter {
defaultActionVisibility: utils.DEFAULT_VISIBILITY,
features: [...SUPPORTED_FEATURES],
componentName: null,
eventsEmmited: {},
eventsEmitted: {},
identifiers: {},
imports: {},
ast: getAstDefaultOptions(),
Expand All @@ -153,7 +110,7 @@ class Parser extends EventEmitter {
Parser.normalizeOptions(options);

// Check the presence and basic format of multiple options
for (const key of ['eventsEmmited', 'identifiers', 'imports']) {
for (const key of ['eventsEmitted', 'identifiers', 'imports']) {
const hasKey = (key in options);

if (!hasKey) {
Expand All @@ -175,26 +132,7 @@ class Parser extends EventEmitter {
});
}

if ('features' in options) {
if (!Array.isArray(options.features)) {
throw new TypeError('options.features must be an array');
}

if (options.features.length === 0) {
throw new Error(
'options.features must contain at least one feature. ' +
INFO_FEATURES_SUPPORTED
);
}

if (!options.features.every(isFeatureSupported)) {
const notSupported = options.features.filter(
(iv) => !isFeatureSupported(iv)
);

throw new Error(getUnsupporteFeaturesString(notSupported));
}
}
validateFeatures(options, SUPPORTED_FEATURES);
}

static getEventName(feature) {
Expand Down Expand Up @@ -276,21 +214,21 @@ class Parser extends EventEmitter {
keywords: entry.keywords
};

if (hasOwnProperty(this.eventsEmmited, event.name)) {
const emitedEvent = this.eventsEmmited[event.name];
if (hasOwnProperty(this.eventsEmitted, event.name)) {
const emitedEvent = this.eventsEmitted[event.name];

if (emitedEvent.parent) {
event.visibility = 'public';

this.eventsEmmited[event.name] = event;
this.eventsEmitted[event.name] = event;

this.parseKeywords(entry.keywords, event);
this.emit('event', event);
} else {
// This event already defined
}
} else {
this.eventsEmmited[event.name] = event;
this.eventsEmitted[event.name] = event;

this.parseKeywords(entry.keywords, event);
this.emit('event', event);
Expand Down Expand Up @@ -617,15 +555,15 @@ class Parser extends EventEmitter {
if (!event.name) {
event.name = '****unhandled-event-name****';
} else {
if (hasOwnProperty(this.eventsEmmited, event.name)) {
const emitedEvent = this.eventsEmmited[event.name];
if (hasOwnProperty(this.eventsEmitted, event.name)) {
const emitedEvent = this.eventsEmitted[event.name];

if (emitedEvent.visibility === 'public') {
continue;
}
}

this.eventsEmmited[event.name] = event;
this.eventsEmitted[event.name] = event;
}

this.parseKeywords(event.keywords, event);
Expand Down Expand Up @@ -769,8 +707,8 @@ class Parser extends EventEmitter {
event.description = comment.description || '';
event.keywords = comment.keywords;

if (!hasOwnProperty(this.eventsEmmited, event.name)) {
this.eventsEmmited[event.name] = event;
if (!hasOwnProperty(this.eventsEmitted, event.name)) {
this.eventsEmitted[event.name] = event;

this.parseKeywords(comment.keywords, event);
this.emit('event', event);
Expand Down
Loading

0 comments on commit 52b6371

Please sign in to comment.