-
Notifications
You must be signed in to change notification settings - Fork 55
/
index.ts
118 lines (103 loc) · 3.6 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/* eslint-disable no-fallthrough */
import { IncludeExcludeOptions, isExportStory } from '@storybook/csf'
import { ASTUtils, TSESTree, TSESLint } from '@typescript-eslint/utils'
import {
isFunctionDeclaration,
isIdentifier,
isObjectExpression,
isSpreadElement,
isTSAsExpression,
isTSSatisfiesExpression,
isVariableDeclaration,
isVariableDeclarator,
} from './ast'
export const docsUrl = (ruleName: string) =>
`https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/${ruleName}.md`
export const getMetaObjectExpression = (
node: TSESTree.ExportDefaultDeclaration,
context: Readonly<TSESLint.RuleContext<string, readonly unknown[]>>
) => {
let meta: TSESTree.ExportDefaultDeclaration['declaration'] | null = node.declaration
const { sourceCode } = context
if (isIdentifier(meta)) {
// Compatibility implementation for eslint v8.x and v9.x or later
// see https://eslint.org/blog/2023/09/preparing-custom-rules-eslint-v9/#context.getscope()
const scope = sourceCode.getScope ? sourceCode.getScope(node) : context.getScope()
const variable = ASTUtils.findVariable(scope, meta.name)
const decl = variable && variable.defs.find((def) => isVariableDeclarator(def.node))
if (decl && isVariableDeclarator(decl.node)) {
meta = decl.node.init
}
}
if (isTSAsExpression(meta) || isTSSatisfiesExpression(meta)) {
meta = meta.expression
}
return isObjectExpression(meta) ? meta : null
}
export const getDescriptor = (
metaDeclaration: TSESTree.ObjectExpression,
propertyName: string
): string[] | RegExp | undefined => {
const property =
metaDeclaration &&
metaDeclaration.properties.find(
(p) => 'key' in p && 'name' in p.key && p.key.name === propertyName
)
if (!property || isSpreadElement(property)) {
return undefined
}
const { type } = property.value
switch (type) {
case 'ArrayExpression':
return property.value.elements.map((t) => {
if (t === null) {
throw new Error(`Unexpected descriptor element: null`)
}
if (!['StringLiteral', 'Literal'].includes(t.type)) {
throw new Error(`Unexpected descriptor element: ${t.type}`)
}
// @ts-expect-error TODO: t should be only StringLiteral or Literal, and the type is not resolving correctly
return t.value
})
case 'Literal':
// @ts-expect-error TODO: Investigation needed. Type systems says, that "RegExpLiteral" does not exist
case 'RegExpLiteral':
// @ts-expect-error TODO: investigation needed
return property.value.value
default:
throw new Error(`Unexpected descriptor: ${type}`)
}
}
export const isValidStoryExport = (
node: TSESTree.Identifier,
nonStoryExportsConfig: IncludeExcludeOptions
) => isExportStory(node.name, nonStoryExportsConfig) && node.name !== '__namedExportsOrder'
export const getAllNamedExports = (node: TSESTree.ExportNamedDeclaration) => {
// e.g. `export { MyStory }`
if (!node.declaration && node.specifiers) {
return node.specifiers.reduce((acc, specifier) => {
if (isIdentifier(specifier.exported)) {
acc.push(specifier.exported)
}
return acc
}, [] as TSESTree.Identifier[])
}
const decl = node.declaration
if (isVariableDeclaration(decl)) {
const declaration = decl.declarations[0]
if (declaration) {
const { id } = declaration
// e.g. `export const MyStory`
if (isIdentifier(id)) {
return [id]
}
}
}
if (isFunctionDeclaration(decl)) {
// e.g. `export function MyStory() {}`
if (isIdentifier(decl.id)) {
return [decl.id]
}
}
return []
}