diff --git a/ERRORS.md b/ERRORS.md new file mode 100644 index 00000000..1c6d7bf0 --- /dev/null +++ b/ERRORS.md @@ -0,0 +1,125 @@ +# Errors + +List of errors that can be thrown by this addon. + +## `PARSER_EXTRACT_SVELTE` + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_1` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_2` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_3` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_4` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_5` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_6` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_7` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_SVELTE_8` + + + +## `PARSER_EXTRACT_COMPILED` + +### `SB_SVELTE_CSF_PARSER_EXTRACT_COMPILED_1` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_COMPILED_2` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_COMPILED_3` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_COMPILED_4` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_COMPILED_5` + + + +### `SB_SVELTE_CSF_PARSER_EXTRACT_COMPILED_6` + +## `PARSER_ANALYSE_DEFINE_META` + +### `SB_SVELTE_CSF_PARSER_ANALYSE_DEFINE_META_1` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_DEFINE_META_2` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_DEFINE_META_3` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_DEFINE_META_4` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_DEFINE_META_5` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_DEFINE_META_6` + +## `PARSER_ANALYSE_STORY` + +### `SB_SVELTE_CSF_PARSER_ANALYSE_STORY_1` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_STORY_2` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_STORY_3` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_STORY_4` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_STORY_5` + + + +### `SB_SVELTE_CSF_PARSER_ANALYSE_STORY_6` + + + +## `COMPILER` + +### `SB_SVELTE_CSF_COMPILER_1` + + + +### `SB_SVELTE_CSF_COMPILER_2` + + + +### `SB_SVELTE_CSF_COMPILER_3` + + diff --git a/src/indexer/index.ts b/src/indexer/index.ts index 509f5b22..9d151737 100644 --- a/src/indexer/index.ts +++ b/src/indexer/index.ts @@ -70,6 +70,7 @@ export const indexer: Indexer = { attributes: ['exportName', 'name', 'tags'], }); const { exportName, name } = getStoryIdentifiers({ + component, nameNode: attributeNode.name, exportNameNode: attributeNode.exportName, filename, diff --git a/src/parser/analyse/story/attributes/identifiers.test.ts b/src/parser/analyse/story/attributes/identifiers.test.ts index 5172c2c5..2106af34 100644 --- a/src/parser/analyse/story/attributes/identifiers.test.ts +++ b/src/parser/analyse/story/attributes/identifiers.test.ts @@ -77,7 +77,10 @@ describe(getStoryIdentifiers.name, () => { filename: 'invalid.stories.svelte', }) ).toThrowErrorMatchingInlineSnapshot( - `[Error: Missing 'name' or 'exportName' prop in a definition in the 'invalid.stories.svelte' file. All stories must either have a 'name' or an 'exportName' prop.]` + ` + [SB_SVELTE_CSF_PARSER_ANALYSE_STORY_0004 (NoStoryIdentifierError): Missing 'name' or 'exportName' attribute (prop) in a '' definition in the stories file: 'file:///Users/xeho91/Nextcloud/Projects/oss/addon-svelte-csf/invalid.stories.svelte'. + All stories must either have a 'name' or an 'exportName' prop.] + ` ); }); @@ -177,9 +180,11 @@ describe(getStoryIdentifiers.name, () => { }) ).toThrowErrorMatchingInlineSnapshot( ` - [Error: Invalid exportName 'default' found in component in 'invalid.stories.svelte'. - exportName must be a valid JavaScript variable name. It must start with a letter, $ or _, followed by letters, numbers, $ or _. - Reserved words like 'default' are also not allowed (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words)] + [SB_SVELTE_CSF_PARSER_ANALYSE_STORY_0005 (InvalidStoryExportNameError): Invalid attribute 'exportName' value 'default' found in '' component inside stories file: file:///Users/xeho91/Nextcloud/Projects/oss/addon-svelte-csf/invalid.stories.svelte + + 'exportName' alue must be a valid JavaScript variable name. + It must start with a letter, $ or _, followed by letters, numbers, $ or _. + Reserved words like 'default' are also not allowed (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words)] ` ); }); @@ -244,11 +249,13 @@ describe(getStoriesIdentifiers.name, () => { }) ).toThrowErrorMatchingInlineSnapshot( ` - [Error: Duplicate exportNames found between two definitions in 'duplicate-identifiers.stories.svelte': + [SB_SVELTE_CSF_PARSER_ANALYSE_STORY_0006 (DuplicateStoryIdentifiersError): Duplicate exportNames found between two '' definitions in stories file: file:///Users/xeho91/Nextcloud/Projects/oss/addon-svelte-csf/duplicate-identifiers.stories.svelte + First instance: Second instance: - This can happen when 'exportName' is implicitly derived by 'name'. Complex names will be simplified to a PascalCased, valid JavaScript variable name, + This can happen when 'exportName' is implicitly derived by 'name'. + Complex names will be simplified to a PascalCased, valid JavaScript variable name, eg. 'Some story name!!' will be converted to 'SomeStoryName'. You can fix this collision by providing a unique 'exportName' prop with .] ` @@ -275,11 +282,13 @@ describe(getStoriesIdentifiers.name, () => { }) ).toThrowErrorMatchingInlineSnapshot( ` - [Error: Duplicate exportNames found between two definitions in 'duplicate-identifiers.stories.svelte': + [SB_SVELTE_CSF_PARSER_ANALYSE_STORY_0006 (DuplicateStoryIdentifiersError): Duplicate exportNames found between two '' definitions in stories file: file:///Users/xeho91/Nextcloud/Projects/oss/addon-svelte-csf/duplicate-identifiers.stories.svelte + First instance: Second instance: - This can happen when 'exportName' is implicitly derived by 'name'. Complex names will be simplified to a PascalCased, valid JavaScript variable name, + This can happen when 'exportName' is implicitly derived by 'name'. + Complex names will be simplified to a PascalCased, valid JavaScript variable name, eg. 'Some story name!!' will be converted to 'SomeStoryName'. You can fix this collision by providing a unique 'exportName' prop with .] ` diff --git a/src/parser/analyse/story/attributes/identifiers.ts b/src/parser/analyse/story/attributes/identifiers.ts index 3196f5fc..0028993c 100644 --- a/src/parser/analyse/story/attributes/identifiers.ts +++ b/src/parser/analyse/story/attributes/identifiers.ts @@ -1,11 +1,15 @@ -import dedent from 'dedent'; -import type { Attribute } from 'svelte/compiler'; +import type { Attribute, Component } from 'svelte/compiler'; import { getStringValueFromAttribute } from '../attributes'; import type { SvelteASTNodes } from '#parser/extract/svelte/nodes'; import { extractStoryAttributesNodes } from '#parser/extract/svelte/story/attributes'; import { isValidVariableName, storyNameToExportName } from '#utils/identifier-utils'; +import { + DuplicateStoryIdentifiersError, + InvalidStoryExportNameError, + NoStoryIdentifierError, +} from '#utils/error/parser/analyse/story'; type StoryIdentifiers = { exportName: string; @@ -16,31 +20,39 @@ interface GetIdentifiersParams { nameNode?: Attribute | undefined; exportNameNode?: Attribute | undefined; filename?: string; + component: Component; } export function getStoryIdentifiers(options: GetIdentifiersParams): StoryIdentifiers { - const { nameNode, exportNameNode, filename } = options; + const { nameNode, exportNameNode, filename, component } = options; let exportName = getStringValueFromAttribute({ node: exportNameNode, filename, + component, + }); + const name = getStringValueFromAttribute({ + node: nameNode, + filename, + component, }); - const name = getStringValueFromAttribute({ node: nameNode, filename }); if (!exportName) { if (!name) { - throw new Error( - `Missing 'name' or 'exportName' prop in a definition in the '${filename}' file. All stories must either have a 'name' or an 'exportName' prop.` - ); + throw new NoStoryIdentifierError({ + component, + filename, + }); } exportName = storyNameToExportName(name); } if (!isValidVariableName(exportName)) { - throw new Error(dedent`Invalid exportName '${exportName}' found in component in '${filename}'. - exportName must be a valid JavaScript variable name. It must start with a letter, $ or _, followed by letters, numbers, $ or _. - Reserved words like 'default' are also not allowed (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words) - `); + throw new InvalidStoryExportNameError({ + filename, + component, + value: exportName, + }); } return { @@ -70,6 +82,7 @@ export function getStoriesIdentifiers(params: GetStoriesIdentifiersParams): Stor exportNameNode: exportName, nameNode: name, filename, + component: story.component, }); const duplicateIdentifiers = allIdentifiers.find( @@ -77,14 +90,11 @@ export function getStoriesIdentifiers(params: GetStoriesIdentifiersParams): Stor ); if (duplicateIdentifiers) { - throw new Error(dedent`Duplicate exportNames found between two definitions in '${filename}': - First instance: - Second instance: - - This can happen when 'exportName' is implicitly derived by 'name'. Complex names will be simplified to a PascalCased, valid JavaScript variable name, - eg. 'Some story name!!' will be converted to 'SomeStoryName'. - You can fix this collision by providing a unique 'exportName' prop with . - `); + throw new DuplicateStoryIdentifiersError({ + filename, + identifiers, + duplicateIdentifiers, + }); } allIdentifiers.push(identifiers); diff --git a/src/utils/error/parser/analyse/define-meta.ts b/src/utils/error/parser/analyse/define-meta.ts index 329b8148..dbe09c30 100644 --- a/src/utils/error/parser/analyse/define-meta.ts +++ b/src/utils/error/parser/analyse/define-meta.ts @@ -12,7 +12,7 @@ export class InvalidComponentValueError extends StorybookSvelteCSFError { filename, componentProperty, }: { - filename: StorybookSvelteCSFError['storiesFilename']; + filename: StorybookSvelteCSFError['filename']; componentProperty: InvalidComponentValueError['componentProperty']; }) { super({ filename }); @@ -41,7 +41,7 @@ export class NoDestructuredDefineMetaCallError extends StorybookSvelteCSFError { filename, defineMetaVariableDeclarator, }: { - filename: StorybookSvelteCSFError['storiesFilename']; + filename: StorybookSvelteCSFError['filename']; defineMetaVariableDeclarator: NoDestructuredDefineMetaCallError['defineMetaVariableDeclarator']; }) { super({ filename }); @@ -68,7 +68,7 @@ export class NoMetaIdentifierFoundError extends StorybookSvelteCSFError { readonly category = StorybookSvelteCSFError.CATEGORY.parserAnalyseDefineMeta; readonly code = 3; - constructor(filename: StorybookSvelteCSFError['storiesFilename']) { + constructor(filename: StorybookSvelteCSFError['filename']) { super({ filename }); } @@ -97,7 +97,7 @@ export class NoStringLiteralError extends StorybookSvelteCSFError { filename, property, }: { - filename: StorybookSvelteCSFError['storiesFilename']; + filename: StorybookSvelteCSFError['filename']; property: NoStringLiteralError['property']; }) { super({ filename }); @@ -126,7 +126,7 @@ export class NoArrayExpressionError extends StorybookSvelteCSFError { filename, property, }: { - filename: StorybookSvelteCSFError['storiesFilename']; + filename: StorybookSvelteCSFError['filename']; property: NoArrayExpressionError['property']; }) { super({ filename }); @@ -157,7 +157,7 @@ export class ArrayElementNotStringError extends StorybookSvelteCSFError { property, element, }: { - filename: StorybookSvelteCSFError['storiesFilename']; + filename: StorybookSvelteCSFError['filename']; property: ArrayElementNotStringError['property']; element: ArrayElementNotStringError['element']; }) { diff --git a/src/utils/error/parser/analyse/story.ts b/src/utils/error/parser/analyse/story.ts index 63b9dc63..dbc16b2b 100644 --- a/src/utils/error/parser/analyse/story.ts +++ b/src/utils/error/parser/analyse/story.ts @@ -3,6 +3,7 @@ import type { Attribute } from 'svelte/compiler'; import { StorybookSvelteCSFError } from '#utils/error'; import type { ArrayExpression, Literal } from 'estree'; +import type { getStoryIdentifiers } from '#parser/analyse/story/attributes/identifiers'; export class AttributeNotStringError extends StorybookSvelteCSFError { readonly category = StorybookSvelteCSFError.CATEGORY.parserAnalyseStory; @@ -123,3 +124,91 @@ export class AttributeNotArrayOfStringsError extends StorybookSvelteCSFError { `; } } + +export class NoStoryIdentifierError extends StorybookSvelteCSFError { + readonly category = StorybookSvelteCSFError.CATEGORY.parserAnalyseStory; + readonly code = 4; + + constructor({ + filename, + component, + }: { + filename: StorybookSvelteCSFError['filename']; + component: NonNullable; + }) { + super({ component, filename }); + } + + template(): string { + return dedent` + Missing 'name' or 'exportName' attribute (prop) in a '' definition in the stories file: '${this.filepathURL}'. + All stories must either have a 'name' or an 'exportName' prop. + `; + } +} + +export class InvalidStoryExportNameError extends StorybookSvelteCSFError { + readonly category = StorybookSvelteCSFError.CATEGORY.parserAnalyseStory; + readonly code = 5; + + public value: string; + + constructor({ + filename, + component, + value, + }: { + filename: StorybookSvelteCSFError['filename']; + component: NonNullable; + value: InvalidStoryExportNameError['value']; + }) { + super({ component, filename }); + this.value = value; + } + + template(): string { + return dedent` + Invalid attribute 'exportName' value '${this.value}' found in '' component inside stories file: ${this.filepathURL} + + 'exportName' alue must be a valid JavaScript variable name. + It must start with a letter, $ or _, followed by letters, numbers, $ or _. + Reserved words like 'default' are also not allowed (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#reserved_words) + `; + } +} + +export class DuplicateStoryIdentifiersError extends StorybookSvelteCSFError { + readonly category = StorybookSvelteCSFError.CATEGORY.parserAnalyseStory; + readonly code = 6; + + public identifiers: ReturnType; + public duplicateIdentifiers: NonNullable>; + + constructor({ + filename, + identifiers, + duplicateIdentifiers, + }: { + filename: StorybookSvelteCSFError['filename']; + identifiers: DuplicateStoryIdentifiersError['identifiers']; + duplicateIdentifiers: DuplicateStoryIdentifiersError['duplicateIdentifiers']; + }) { + super({ filename }); + this.identifiers = identifiers; + this.duplicateIdentifiers = duplicateIdentifiers; + } + + template(): string { + return dedent` + Duplicate exportNames found between two '' definitions in stories file: ${this.filepathURL} + + First instance: + Second instance: + + This can happen when 'exportName' is implicitly derived by 'name'. + Complex names will be simplified to a PascalCased, valid JavaScript variable name, + eg. 'Some story name!!' will be converted to 'SomeStoryName'. + You can fix this collision by providing a unique 'exportName' prop with . + `; + } +}