UNPKG

7.4 kBPlain TextView Raw
1/**
2 * @JsxAttribute utilities for react rules.
3 */
4
5import * as ts from 'typescript';
6import {
7 isJsxAttribute,
8 isJsxExpression,
9 isStringLiteral,
10 isNumericLiteral,
11 isJsxElement,
12 isJsxSelfClosingElement,
13 isJsxOpeningElement,
14 isFalseKeyword,
15 isTrueKeyword,
16} from './TypeGuard';
17
18/**
19 * Get a dictionary of JsxAttribute from a JsxElement, JsxSelfClosingElement or JsxOpeningElement.
20 * @returns { [propName: string]: ts.JsxAttribute } a dictionary has lowercase keys.
21 */
22export function getJsxAttributesFromJsxElement(node: ts.Node): { [propName: string]: ts.JsxAttribute } {
23 const attributesDictionary: { [propName: string]: ts.JsxAttribute } = {};
24
25 getAllAttributesFromJsxElement(node).forEach(attr => {
26 if (isJsxAttribute(attr)) {
27 attributesDictionary[getPropName(attr).toLowerCase()] = attr;
28 }
29 });
30
31 return attributesDictionary;
32}
33
34/**
35 * Get an array of attributes in the given node.
36 * It contains JsxAttribute and JsxSpreadAttribute.
37 */
38export function getAllAttributesFromJsxElement(node: ts.Node): ts.NodeArray<ts.JsxAttributeLike> {
39 let attributes: ts.NodeArray<ts.JsxAttributeLike>;
40
41 if (node == null) {
42 return <ts.NodeArray<ts.JsxAttributeLike>>(<any>[]);
43 } else if (isJsxElement(node)) {
44 attributes = node.openingElement.attributes.properties;
45 } else if (isJsxSelfClosingElement(node)) {
46 attributes = node.attributes.properties;
47 } else if (isJsxOpeningElement(node)) {
48 attributes = node.attributes.properties;
49 } else {
50 throw new Error('The node must be a JsxElement, JsxSelfClosingElement or JsxOpeningElement.');
51 }
52
53 return attributes;
54}
55
56export function getPropName(node: ts.JsxAttribute): string {
57 if (!isJsxAttribute(node)) {
58 throw new Error('The node must be a JsxAttribute collected by the AST parser.');
59 }
60
61 return node.name ? node.name.text : undefined;
62}
63
64/**
65 * Get first JsxElement whose tagName equals tagName from code.
66 * @param code - a string of jsx code.
67 * @param exceptTagName - the element's tagName you want to get.
68 * @return { ts.JsxElement | ts.JsxSelfClosingElement } - a element.
69 */
70export function getJsxElementFromCode(code: string, exceptTagName: string): ts.JsxElement | ts.JsxSelfClosingElement {
71 const sourceFile: ts.SourceFile = ts.createSourceFile('test.tsx', code, ts.ScriptTarget.ES2015, true);
72
73 return delintNode(sourceFile, exceptTagName);
74}
75
76function delintNode(node: ts.Node, tagName: string): ts.JsxElement | ts.JsxSelfClosingElement {
77 if (isJsxElement(node) && node.openingElement.tagName.getText() === tagName) {
78 return node;
79 } else if (isJsxSelfClosingElement(node) && node.tagName.getText() === tagName) {
80 return node;
81 } else if (!node || node.getChildCount() === 0) {
82 return undefined;
83 }
84
85 return ts.forEachChild(node, (childNode: ts.Node) => delintNode(childNode, tagName));
86}
87
88/**
89 * Get ancestor node whose tagName is ancestorTagName for a node.
90 * @return { ts.JsxElement } the ancestor node or undefined if the ancestor node is not exist.
91 */
92export function getAncestorNode(node: ts.Node, ancestorTagName: string): ts.JsxElement {
93 if (!node) {
94 return undefined;
95 }
96
97 const ancestorNode: ts.Node = node.parent;
98
99 if (isJsxElement(ancestorNode) && ancestorNode.openingElement.tagName.getText() === ancestorTagName) {
100 return ancestorNode;
101 } else {
102 return getAncestorNode(ancestorNode, ancestorTagName);
103 }
104}
105
106/**
107 * Get the boolean literal in jsx attribute initializer with following format:
108 * @example
109 * <div attribute={ true } />
110 * @example
111 * <div attribute='true' />
112 * @example
113 * <div attribute={ 'true' } />
114 */
115export function getBooleanLiteral(node: ts.JsxAttribute): boolean {
116 if (!isJsxAttribute(node)) {
117 throw new Error('The node must be a JsxAttribute collected by the AST parser.');
118 }
119
120 const initializer: ts.Expression = node == null ? null : node.initializer;
121 const getBooleanFromString: (value: string) => boolean = (value: string) => {
122 if (value.toLowerCase() === 'true') {
123 return true;
124 } else if (value.toLowerCase() === 'false') {
125 return false;
126 } else {
127 return undefined;
128 }
129 };
130
131 if (isStringLiteral(initializer)) {
132 return getBooleanFromString(initializer.text);
133 } else if (isJsxExpression(initializer)) {
134 const expression: ts.Expression = initializer.expression;
135
136 if (isStringLiteral(expression)) {
137 return getBooleanFromString(expression.text);
138 } else {
139 if (isTrueKeyword(expression)) {
140 return true;
141 } else if (isFalseKeyword(expression)) {
142 return false;
143 } else {
144 return undefined;
145 }
146 }
147 }
148
149 return false;
150}
151
152/**
153 * Get the numeric literal in jsx attribute initializer with following format:
154 * @example
155 * <div attribute={ 1 } />
156 */
157export function getNumericLiteral(node: ts.JsxAttribute): string {
158 if (!isJsxAttribute(node)) {
159 throw new Error('The node must be a JsxAttribute collected by the AST parser.');
160 }
161
162 const initializer: ts.Expression = node == null ? null : node.initializer;
163
164 return isJsxExpression(initializer) && isNumericLiteral(initializer.expression)
165 ? (<ts.LiteralExpression>initializer.expression).text
166 : undefined;
167}
168
169/**
170 * Get the string literal in jsx attribute initializer with following format:
171 * @example
172 * <div attribute='StringLiteral' />
173 * @example
174 * <div attribute={ 'StringLiteral' } />
175 */
176export function getStringLiteral(node: ts.JsxAttribute | ts.JsxSpreadAttribute): string {
177 if (!isJsxAttribute(node)) {
178 throw new Error('The node must be a JsxAttribute collected by the AST parser.');
179 }
180
181 const initializer: ts.Expression = node == null ? null : node.initializer;
182
183 if (!initializer) {
184 // <tag attribute/>
185 return '';
186 } else if (isStringLiteral(initializer)) {
187 // <tag attribute='value' />
188 return initializer.text.trim();
189 } else if (isJsxExpression(initializer) && isStringLiteral(initializer.expression)) {
190 // <tag attribute={'value'} />
191 return (<ts.StringLiteral>initializer.expression).text;
192 } else if (isJsxExpression(initializer) && !initializer.expression) {
193 // <tag attribute={} />
194 return '';
195 } else {
196 return undefined;
197 }
198}
199
200export function isEmpty(node: ts.JsxAttribute): boolean {
201 const initializer: ts.Expression = node == null ? null : node.initializer;
202
203 if (initializer == null) {
204 return true;
205 } else if (isStringLiteral(initializer)) {
206 return initializer.text.trim() === '';
207 } else if (initializer.kind === ts.SyntaxKind.Identifier) {
208 return initializer.getText() === 'undefined';
209 } else if (initializer.kind === ts.SyntaxKind.NullKeyword) {
210 return true;
211 } else if ((<any>initializer).expression != null) {
212 const expression: ts.Expression = (<any>initializer).expression;
213 if (expression.kind === ts.SyntaxKind.Identifier) {
214 return expression.getText() === 'undefined';
215 } else if (expression.kind === ts.SyntaxKind.NullKeyword) {
216 return true;
217 }
218 }
219 return false;
220}