Skip to content

Commit 6260880

Browse files
committed
feat: add StringLiteral and TemplateLiteral type checks
1 parent 1c1497a commit 6260880

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

lib/node-utils/accessors.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import {
2+
AST_NODE_TYPES,
3+
ASTUtils,
4+
type TSESTree,
5+
} from '@typescript-eslint/utils';
6+
7+
/**
8+
* A `Literal` with a `value` of type `string`.
9+
*/
10+
interface StringLiteral<Value extends string = string>
11+
extends TSESTree.StringLiteral {
12+
value: Value;
13+
}
14+
15+
/**
16+
* Checks if the given `node` is a `StringLiteral`.
17+
*
18+
* If a `value` is provided & the `node` is a `StringLiteral`,
19+
* the `value` will be compared to that of the `StringLiteral`.
20+
*
21+
* @param {Node} node
22+
* @param {V} [value]
23+
*
24+
* @return {node is StringLiteral<V>}
25+
*
26+
* @template V
27+
*/
28+
const isStringLiteral = <V extends string>(
29+
node: TSESTree.Node,
30+
value?: V
31+
): node is StringLiteral<V> =>
32+
node.type === AST_NODE_TYPES.Literal &&
33+
typeof node.value === 'string' &&
34+
(value === undefined || node.value === value);
35+
36+
interface TemplateLiteral<Value extends string = string>
37+
extends TSESTree.TemplateLiteral {
38+
quasis: [TSESTree.TemplateElement & { value: { raw: Value; cooked: Value } }];
39+
}
40+
41+
/**
42+
* Checks if the given `node` is a `TemplateLiteral`.
43+
*
44+
* Complex `TemplateLiteral`s are not considered specific, and so will return `false`.
45+
*
46+
* If a `value` is provided & the `node` is a `TemplateLiteral`,
47+
* the `value` will be compared to that of the `TemplateLiteral`.
48+
*
49+
* @param {Node} node
50+
* @param {V} [value]
51+
*
52+
* @return {node is TemplateLiteral<V>}
53+
*
54+
* @template V
55+
*/
56+
const isTemplateLiteral = <V extends string>(
57+
node: TSESTree.Node,
58+
value?: V
59+
): node is TemplateLiteral<V> =>
60+
node.type === AST_NODE_TYPES.TemplateLiteral &&
61+
node.quasis.length === 1 && // bail out if not simple
62+
(value === undefined || node.quasis[0].value.raw === value);
63+
64+
export type StringNode<S extends string = string> =
65+
| StringLiteral<S>
66+
| TemplateLiteral<S>;
67+
68+
/**
69+
* Checks if the given `node` is a {@link StringNode}.
70+
*
71+
* @param {Node} node
72+
* @param {V} [specifics]
73+
*
74+
* @return {node is StringNode}
75+
*
76+
* @template V
77+
*/
78+
export const isStringNode = <V extends string>(
79+
node: TSESTree.Node,
80+
specifics?: V
81+
): node is StringNode<V> =>
82+
isStringLiteral(node, specifics) || isTemplateLiteral(node, specifics);
83+
84+
/**
85+
* Gets the value of the given `StringNode`.
86+
*
87+
* If the `node` is a `TemplateLiteral`, the `raw` value is used;
88+
* otherwise, `value` is returned instead.
89+
*
90+
* @param {StringNode<S>} node
91+
*
92+
* @return {S}
93+
*
94+
* @template S
95+
*/
96+
export const getStringValue = <S extends string>(node: StringNode<S>): S =>
97+
isTemplateLiteral(node) ? node.quasis[0].value.raw : node.value;
98+
99+
/**
100+
* An `Identifier` with a known `name` value
101+
*/
102+
interface KnownIdentifier<Name extends string> extends TSESTree.Identifier {
103+
name: Name;
104+
}
105+
106+
/**
107+
* Checks if the given `node` is an `Identifier`.
108+
*
109+
* If a `name` is provided, & the `node` is an `Identifier`,
110+
* the `name` will be compared to that of the `identifier`.
111+
*
112+
* @param {Node} node
113+
* @param {V} [name]
114+
*
115+
* @return {node is KnownIdentifier<Name>}
116+
*
117+
* @template V
118+
*/
119+
export const isIdentifier = <V extends string>(
120+
node: TSESTree.Node,
121+
name?: V
122+
): node is KnownIdentifier<V> =>
123+
ASTUtils.isIdentifier(node) && (name === undefined || node.name === name);
124+
125+
/**
126+
* Checks if the given `node` is a "supported accessor".
127+
*
128+
* This means that it's a node can be used to access properties,
129+
* and who's "value" can be statically determined.
130+
*
131+
* `MemberExpression` nodes most commonly contain accessors,
132+
* but it's possible for other nodes to contain them.
133+
*
134+
* If a `value` is provided & the `node` is an `AccessorNode`,
135+
* the `value` will be compared to that of the `AccessorNode`.
136+
*
137+
* Note that `value` here refers to the normalised value.
138+
* The property that holds the value is not always called `name`.
139+
*
140+
* @param {Node} node
141+
* @param {V} [value]
142+
*
143+
* @return {node is AccessorNode<V>}
144+
*
145+
* @template V
146+
*/
147+
export const isSupportedAccessor = <V extends string>(
148+
node: TSESTree.Node,
149+
value?: V
150+
): node is AccessorNode<V> =>
151+
isIdentifier(node, value) || isStringNode(node, value);
152+
153+
/**
154+
* Gets the value of the given `AccessorNode`,
155+
* account for the different node types.
156+
*
157+
* @param {AccessorNode<S>} accessor
158+
*
159+
* @return {S}
160+
*
161+
* @template S
162+
*/
163+
export const getAccessorValue = <S extends string = string>(
164+
accessor: AccessorNode<S>
165+
): S =>
166+
accessor.type === AST_NODE_TYPES.Identifier
167+
? accessor.name
168+
: getStringValue(accessor);
169+
170+
export type AccessorNode<Specifics extends string = string> =
171+
| StringNode<Specifics>
172+
| KnownIdentifier<Specifics>;

0 commit comments

Comments
 (0)