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