diff --git a/index.js b/index.js index 939c621..1018947 100644 --- a/index.js +++ b/index.js @@ -1,15 +1,87 @@ const { loadFileStructureFromOptions } = require('./lib/helpers'); const SvelteVersionDetector = require('./lib/detector'); - +const { isString, printArray, isVisibilitySupported } = require('./lib/utils'); + +/** + * @typedef {import("./typings").SvelteParserOptions} SvelteParserOptions + * @typedef {import('./typings').SymbolVisibility} SymbolVisibility + */ + +/** @type {BufferEncoding[]} */ +const ENCODINGS = [ + 'ascii', + 'utf8', + 'utf-8', + 'utf16le', + 'ucs2', + 'ucs-2', + 'base64', + 'latin1', + 'binary', + 'hex' +]; + +/** @type {SymbolVisibility[]} */ +const VISIBILITIES = ['public', 'protected', 'private']; + +/** @type {BufferEncoding} */ const DEFAULT_ENCODING = 'utf8'; + +/** @type {SymbolVisibility[]} */ const DEFAULT_IGNORED_VISIBILITIES = ['protected', 'private']; +const ERROR_OPTIONS_REQUIRED = 'An options object is required.'; +const ERROR_INPUT_REQUIRED = 'One of options.filename or options.fileContent is required.'; +const ERROR_ENCODING_NOT_SUPPORTED = + 'options.encoding must be one of: ' + printArray(ENCODINGS); + +const ERROR_IGNORED_VISIBILITIES_FORMAT = + 'options.ignoredVisibilities must be an array of those strings: ' + + printArray(VISIBILITIES); + +const ERROR_IGNORED_VISIBILITIES_NOT_SUPPORTED = + `options.ignoredVisibilities expected any of [${printArray(VISIBILITIES)}] ` + + 'but found these instead: '; + +/** + * @param {SvelteParserOptions} options + * @throws an error if any option is invalid + */ function validateOptions(options) { - if (!options || (!options.filename && !options.fileContent)) { - throw new Error('One of options.filename or options.filecontent is required'); + if (!options) { + throw new Error(ERROR_OPTIONS_REQUIRED); + } + + if (!isString(options.filename) && !isString(options.fileContent)) { + throw new Error(ERROR_INPUT_REQUIRED); + } + + if (options.encoding && !ENCODINGS.includes(options.encoding)) { + throw new Error(ERROR_ENCODING_NOT_SUPPORTED); + } + + if (options.ignoredVisibilities) { + if (!Array.isArray(options.ignoredVisibilities)) { + throw new Error(ERROR_IGNORED_VISIBILITIES_FORMAT); + } + + if (!options.ignoredVisibilities.every(isVisibilitySupported)) { + const notSupported = options.ignoredVisibilities.filter( + (iv) => !isVisibilitySupported(iv) + ); + + throw new Error( + ERROR_IGNORED_VISIBILITIES_NOT_SUPPORTED + + printArray(notSupported) + ); + } } } +/** + * Applies default values to options. + * @param {SvelteParserOptions} options + */ function normalizeOptions(options) { options.encoding = options.encoding || DEFAULT_ENCODING; options.ignoredVisibilities = options.ignoredVisibilities || DEFAULT_IGNORED_VISIBILITIES; @@ -179,6 +251,20 @@ function subscribeOnParserEvents(parser, options, version, resolve, reject) { }); } +/** + * Main parse function. + * @param {SvelteParserOptions} options + * @example + * const { parse } = require('sveltedoc-parser'); + * const doc = await parse({ + * filename: 'main.svelte', + * encoding: 'ascii', + * features: ['data', 'computed', 'methods'], + * ignoredVisibilities: ['private'], + * includeSourceLocations: true, + * version: 3 + * }); + */ module.exports.parse = (options) => new Promise((resolve, reject) => { try { validateOptions(options); @@ -198,6 +284,9 @@ module.exports.parse = (options) => new Promise((resolve, reject) => { } }); +/** + * @param {SvelteParserOptions} options + */ module.exports.detectVersion = (options) => { validateOptions(options); diff --git a/lib/utils.js b/lib/utils.js index ebf2912..1686b22 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,10 +1,13 @@ -const RE_VISIBILITY = /(public|protected|private)/; +const VISIBILITIES = ['public', 'protected', 'private']; +const RE_VISIBILITY = new RegExp(`^(${VISIBILITIES.join('|')})$`); const RE_KEYWORDS = /@\**\s*([a-z0-9_-]+)(\s+(-\s+)?([\wÀ-ÿ\s*{}[\]()='"`_^$#&²~|\\£¤€%µ,?;.:/!§<>+¨-]+))?/ig; const DEFAULT_VISIBILITY = 'public'; +const isVisibilitySupported = (v) => RE_VISIBILITY.test(v); + const getVisibility = (keywords, defaultVisibility) => { - const keyword = keywords.find((keyword) => RE_VISIBILITY.test(keyword.name)); + const keyword = keywords.find((keyword) => isVisibilitySupported(keyword.name)); if (keyword) { return keyword.name; @@ -284,7 +287,12 @@ const isTopLevelComment = (comment) => { return comment.keywords.some((keyword) => keyword.name === 'component'); }; +const isString = (x) => typeof x === 'string' || x instanceof String; + +const printArray = (array) => array.map(s => `'${s}'`).join(', '); + module.exports.DEFAULT_VISIBILITY = DEFAULT_VISIBILITY; +module.exports.isVisibilitySupported = isVisibilitySupported; module.exports.getVisibility = getVisibility; module.exports.parseComment = parseComment; module.exports.getCommentFromSourceCode = getCommentFromSourceCode; @@ -300,3 +308,5 @@ module.exports.escapeImportKeyword = escapeImportKeyword; module.exports.inferTypeFromVariableDeclaration = inferTypeFromVariableDeclaration; module.exports.isTopLevelComment = isTopLevelComment; module.exports.buildCamelCase = buildCamelCase; +module.exports.isString = isString; +module.exports.printArray = printArray; diff --git a/typings.d.ts b/typings.d.ts index 62da62b..d1e2b4e 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -18,7 +18,7 @@ export interface JSDocType { /** * Kind of this type. */ - kind: 'type'|'union'|'const', + kind: 'type' | 'union' | 'const', /** * The text representation of this type. */ @@ -28,7 +28,7 @@ export interface JSDocType { * @see `'type'|'const'` in `kind` field, then this field provide the name of JS type. * @see `'union'` in `kind` field, then this field provide the list of @see JSDocType */ - type: string|JSDocType[], + type: string | JSDocType[], /** * The constant value related to this type, if can be provided. */ @@ -50,7 +50,7 @@ export interface JSDocTypeElement extends JSDocType { /** * Kind of this type. */ - kind: 'type'|'const', + kind: 'type' | 'const', /** * The type representation of this item. */ @@ -87,11 +87,11 @@ export interface ISvelteItem { /** * The description of the item, provided from related comment. */ - description?: string|null; + description?: string | null; /** * The visibility of item. */ - visibility?: 'public'|'protected'|'private'; + visibility?: 'public' | 'protected' | 'private'; /** * The list of parsed JSDoc keywords from related comment. */ @@ -120,7 +120,7 @@ export interface SvelteDataItem extends ISvelteItem { * @since Svelte V3 * @since {2.0.0} */ - kind?: 'var'|'let'|'const'; + kind?: 'var' | 'let' | 'const'; /** * Provides information about property binding. * @since Svelte V3 @@ -179,7 +179,7 @@ export interface SvelteComputedItem extends ISvelteItem { /** * The list of data or computed properties names, marked as depended to this property. */ - dependencies: string[] + dependencies: string[] } export interface SvelteMethodParamItem { @@ -259,13 +259,13 @@ export interface SvelteComponentItem extends ISvelteItem { * @since Svelte V3 * @since {2.0.0} */ -export type SvelteEventModificator = 'preventDefault'|'stopPropagation'|'passive'|'capture'|'once'; +export type SvelteEventModificator = 'preventDefault' | 'stopPropagation' | 'passive' | 'capture' | 'once'; export interface SvelteEventItem extends ISvelteItem { /** * The name of HTML element if propagated standart JS Dom event or null. */ - parent?: string|null; + parent?: string | null; /** * The list of event modificators. @@ -295,7 +295,7 @@ export interface SvelteRefItem extends ISvelteItem { /** * The name of HTML element or component that binded with this ref name. */ - parent?: string|null; + parent?: string | null; } /** @@ -305,7 +305,7 @@ export interface SvelteComponentDoc { /** * The name of the parsed component. */ - name?: string|null; + name?: string | null; /** * The Svelte compiler version that used for this document. */ @@ -313,7 +313,7 @@ export interface SvelteComponentDoc { /** * The component description. */ - description?: string|null; + description?: string | null; /** * The list of defined model properties. @@ -323,7 +323,7 @@ export interface SvelteComponentDoc { * The list of defined computed properties of component. */ computed?: SvelteComputedItem[]; - + /** * The list of included components. */ @@ -364,4 +364,122 @@ export interface SvelteComponentDoc { * @since {2.1.0} */ dispatchers?: SvelteMethodItem[]; -} \ No newline at end of file +} + +/** + * Features supported by the Svelte 2 parser. + */ +export enum Svelte2Feature { + name = 'name', + data = 'data', + computed = 'computed', + methods = 'methods', + actions = 'actions', + helpers = 'helpers', + components = 'components', + description = 'description', + keywords = 'keywords', + events = 'events', + slots = 'slots', + transitions = 'transitions', + refs = 'refs', + store = 'store', +}; + +/** + * Features supported by the Svelte 3 parser. + */ +export enum Svelte3Feature { + name = 'name', + data = 'data', + computed = 'computed', + methods = 'methods', + components = 'components', + description = 'description', + keywords = 'keywords', + events = 'events', + slots = 'slots', + refs = 'refs', +}; + +type Svelte2FeatureKeys = keyof typeof Svelte2Feature; +type Svelte3FeatureKeys = keyof typeof Svelte3Feature; +type SvelteFeatureKeys = Svelte2FeatureKeys & Svelte3FeatureKeys; +type Svelte2ExclusiveFeature = Exclude; +type Svelte3ExclusiveFeature = Exclude; + +/** + * Visibility of a Svelte item. + */ +export type SymbolVisibility = 'private' | 'protected' | 'public'; + +/** + * Supported Svelte versions. + */ +export type SvelteVersion = 2 | 3; + +export interface ParserOptions { + /** + * The filename to parse. Required, unless fileContent is passed. + */ + filename?: string; + + /** + * The file content to parse. Required, unless filename is passed. + */ + fileContent?: string; + + /** + * @default 'utf8' + */ + encoding?: BufferEncoding; + + /** + * The component features to parse and extract. + * Uses all supported features by default. + * @see Svelte2Feature + * @see Svelte3Feature + */ + features?: F[]; + + /** + * The list of ignored visibilities. Use an empty array to export all + * visibilities. + * @default ['private','protected'] + */ + ignoredVisibilities?: SymbolVisibility[]; + + /** + * Indicates that source locations should be provided for component symbols. + * @default false + */ + includeSourceLocations?: boolean + + /** + * Optional. Use 2 or 3 to specify which svelte syntax should be used. + * When version is not provided, the parser tries to detect which version + * of the syntax to use. + */ + version?: V; + + /** + * Optional. Specify which version of svelte syntax to fallback to if the + * parser can't identify the version used. + */ + defaultVersion?: V; +} + +/** + * Options to pass to the main parse function. + * + * @example + * const options = { + * filename: 'main.svelte', + * encoding: 'ascii', + * features: ['data', 'computed', 'methods'], + * ignoredVisibilities: ['private'], + * includeSourceLocations: true, + * version: 3 + * }); + */ +export type SvelteParserOptions = ParserOptions<3, Svelte3FeatureKeys> | ParserOptions<2, Svelte2FeatureKeys>;