stylesheet.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. 'use strict';
  2. /**
  3. * StyleSheets represents the StyleSheets found in the source code.
  4. * @constructor
  5. */
  6. function StyleSheets() {
  7. this.styleSheets = {};
  8. }
  9. /**
  10. * Add adds a StyleSheet to our StyleSheets collections.
  11. *
  12. * @param {string} styleSheetName - The name of the StyleSheet.
  13. * @param {object} properties - The collection of rules in the styleSheet.
  14. */
  15. StyleSheets.prototype.add = function (styleSheetName, properties) {
  16. this.styleSheets[styleSheetName] = properties;
  17. };
  18. /**
  19. * MarkAsUsed marks a rule as used in our source code by removing it from the
  20. * specified StyleSheet rules.
  21. *
  22. * @param {string} fullyQualifiedName - The fully qualified name of the rule.
  23. * for example 'styles.text'
  24. */
  25. StyleSheets.prototype.markAsUsed = function (fullyQualifiedName) {
  26. const nameSplit = fullyQualifiedName.split('.');
  27. const styleSheetName = nameSplit[0];
  28. const styleSheetProperty = nameSplit[1];
  29. if (this.styleSheets[styleSheetName]) {
  30. this.styleSheets[styleSheetName] = this
  31. .styleSheets[styleSheetName]
  32. .filter((property) => property.key.name !== styleSheetProperty);
  33. }
  34. };
  35. /**
  36. * GetUnusedReferences returns all collected StyleSheets and their
  37. * unmarked rules.
  38. */
  39. StyleSheets.prototype.getUnusedReferences = function () {
  40. return this.styleSheets;
  41. };
  42. /**
  43. * AddColorLiterals adds an array of expressions that contain color literals
  44. * to the ColorLiterals collection
  45. * @param {array} expressions - an array of expressions containing color literals
  46. */
  47. StyleSheets.prototype.addColorLiterals = function (expressions) {
  48. if (!this.colorLiterals) {
  49. this.colorLiterals = [];
  50. }
  51. this.colorLiterals = this.colorLiterals.concat(expressions);
  52. };
  53. /**
  54. * GetColorLiterals returns an array of collected color literals expressions
  55. * @returns {Array}
  56. */
  57. StyleSheets.prototype.getColorLiterals = function () {
  58. return this.colorLiterals;
  59. };
  60. /**
  61. * AddObjectexpressions adds an array of expressions to the ObjectExpressions collection
  62. * @param {Array} expressions - an array of expressions containing ObjectExpressions in
  63. * inline styles
  64. */
  65. StyleSheets.prototype.addObjectExpressions = function (expressions) {
  66. if (!this.objectExpressions) {
  67. this.objectExpressions = [];
  68. }
  69. this.objectExpressions = this.objectExpressions.concat(expressions);
  70. };
  71. /**
  72. * GetObjectExpressions returns an array of collected object expressiosn used in inline styles
  73. * @returns {Array}
  74. */
  75. StyleSheets.prototype.getObjectExpressions = function () {
  76. return this.objectExpressions;
  77. };
  78. let currentContent;
  79. const getSourceCode = (node) => currentContent
  80. .getSourceCode(node)
  81. .getText(node);
  82. const getStyleSheetObjectNames = (settings) => settings['react-native/style-sheet-object-names'] || ['StyleSheet'];
  83. const astHelpers = {
  84. containsStyleSheetObject: function (node, objectNames) {
  85. return Boolean(
  86. node
  87. && node.type === 'CallExpression'
  88. && node.callee
  89. && node.callee.object
  90. && node.callee.object.name
  91. && objectNames.includes(node.callee.object.name)
  92. );
  93. },
  94. containsCreateCall: function (node) {
  95. return Boolean(
  96. node
  97. && node.callee
  98. && node.callee.property
  99. && node.callee.property.name === 'create'
  100. );
  101. },
  102. isStyleSheetDeclaration: function (node, settings) {
  103. const objectNames = getStyleSheetObjectNames(settings);
  104. return Boolean(
  105. astHelpers.containsStyleSheetObject(node, objectNames)
  106. && astHelpers.containsCreateCall(node)
  107. );
  108. },
  109. getStyleSheetName: function (node) {
  110. if (node && node.parent && node.parent.id) {
  111. return node.parent.id.name;
  112. }
  113. },
  114. getStyleDeclarations: function (node) {
  115. if (
  116. node
  117. && node.type === 'CallExpression'
  118. && node.arguments
  119. && node.arguments[0]
  120. && node.arguments[0].properties
  121. ) {
  122. return node.arguments[0].properties.filter((property) => property.type === 'Property');
  123. }
  124. return [];
  125. },
  126. getStyleDeclarationsChunks: function (node) {
  127. if (
  128. node
  129. && node.type === 'CallExpression'
  130. && node.arguments
  131. && node.arguments[0]
  132. && node.arguments[0].properties
  133. ) {
  134. const { properties } = node.arguments[0];
  135. const result = [];
  136. let chunk = [];
  137. for (let i = 0; i < properties.length; i += 1) {
  138. const property = properties[i];
  139. if (property.type === 'Property') {
  140. chunk.push(property);
  141. } else if (chunk.length) {
  142. result.push(chunk);
  143. chunk = [];
  144. }
  145. }
  146. if (chunk.length) {
  147. result.push(chunk);
  148. }
  149. return result;
  150. }
  151. return [];
  152. },
  153. getPropertiesChunks: function (properties) {
  154. const result = [];
  155. let chunk = [];
  156. for (let i = 0; i < properties.length; i += 1) {
  157. const property = properties[i];
  158. if (property.type === 'Property') {
  159. chunk.push(property);
  160. } else if (chunk.length) {
  161. result.push(chunk);
  162. chunk = [];
  163. }
  164. }
  165. if (chunk.length) {
  166. result.push(chunk);
  167. }
  168. return result;
  169. },
  170. getExpressionIdentifier: function (node) {
  171. if (node) {
  172. switch (node.type) {
  173. case 'Identifier':
  174. return node.name;
  175. case 'Literal':
  176. return node.value;
  177. case 'TemplateLiteral':
  178. return node.quasis.reduce(
  179. (result, quasi, index) => result
  180. + quasi.value.cooked
  181. + astHelpers.getExpressionIdentifier(node.expressions[index]),
  182. ''
  183. );
  184. default:
  185. return '';
  186. }
  187. }
  188. return '';
  189. },
  190. getStylePropertyIdentifier: function (node) {
  191. if (
  192. node
  193. && node.key
  194. ) {
  195. return astHelpers.getExpressionIdentifier(node.key);
  196. }
  197. },
  198. isStyleAttribute: function (node) {
  199. return Boolean(
  200. node.type === 'JSXAttribute'
  201. && node.name
  202. && node.name.name
  203. && node.name.name.toLowerCase().includes('style')
  204. );
  205. },
  206. collectStyleObjectExpressions: function (node, context) {
  207. currentContent = context;
  208. if (astHelpers.hasArrayOfStyleReferences(node)) {
  209. const styleReferenceContainers = node
  210. .expression
  211. .elements;
  212. return astHelpers.collectStyleObjectExpressionFromContainers(
  213. styleReferenceContainers
  214. );
  215. } if (node && node.expression) {
  216. return astHelpers.getStyleObjectExpressionFromNode(node.expression);
  217. }
  218. return [];
  219. },
  220. collectColorLiterals: function (node, context) {
  221. if (!node) {
  222. return [];
  223. }
  224. currentContent = context;
  225. if (astHelpers.hasArrayOfStyleReferences(node)) {
  226. const styleReferenceContainers = node
  227. .expression
  228. .elements;
  229. return astHelpers.collectColorLiteralsFromContainers(
  230. styleReferenceContainers
  231. );
  232. }
  233. if (node.type === 'ObjectExpression') {
  234. return astHelpers.getColorLiteralsFromNode(node);
  235. }
  236. return astHelpers.getColorLiteralsFromNode(node.expression);
  237. },
  238. collectStyleObjectExpressionFromContainers: function (nodes) {
  239. let objectExpressions = [];
  240. nodes.forEach((node) => {
  241. objectExpressions = objectExpressions
  242. .concat(astHelpers.getStyleObjectExpressionFromNode(node));
  243. });
  244. return objectExpressions;
  245. },
  246. collectColorLiteralsFromContainers: function (nodes) {
  247. let colorLiterals = [];
  248. nodes.forEach((node) => {
  249. colorLiterals = colorLiterals
  250. .concat(astHelpers.getColorLiteralsFromNode(node));
  251. });
  252. return colorLiterals;
  253. },
  254. getStyleReferenceFromNode: function (node) {
  255. let styleReference;
  256. let leftStyleReferences;
  257. let rightStyleReferences;
  258. if (!node) {
  259. return [];
  260. }
  261. switch (node.type) {
  262. case 'MemberExpression':
  263. styleReference = astHelpers.getStyleReferenceFromExpression(node);
  264. return [styleReference];
  265. case 'LogicalExpression':
  266. leftStyleReferences = astHelpers.getStyleReferenceFromNode(node.left);
  267. rightStyleReferences = astHelpers.getStyleReferenceFromNode(node.right);
  268. return [].concat(leftStyleReferences).concat(rightStyleReferences);
  269. case 'ConditionalExpression':
  270. leftStyleReferences = astHelpers.getStyleReferenceFromNode(node.consequent);
  271. rightStyleReferences = astHelpers.getStyleReferenceFromNode(node.alternate);
  272. return [].concat(leftStyleReferences).concat(rightStyleReferences);
  273. default:
  274. return [];
  275. }
  276. },
  277. getStyleObjectExpressionFromNode: function (node) {
  278. let leftStyleObjectExpression;
  279. let rightStyleObjectExpression;
  280. if (!node) {
  281. return [];
  282. }
  283. if (node.type === 'ObjectExpression') {
  284. return [astHelpers.getStyleObjectFromExpression(node)];
  285. }
  286. switch (node.type) {
  287. case 'LogicalExpression':
  288. leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.left);
  289. rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.right);
  290. return [].concat(leftStyleObjectExpression).concat(rightStyleObjectExpression);
  291. case 'ConditionalExpression':
  292. leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.consequent);
  293. rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.alternate);
  294. return [].concat(leftStyleObjectExpression).concat(rightStyleObjectExpression);
  295. default:
  296. return [];
  297. }
  298. },
  299. getColorLiteralsFromNode: function (node) {
  300. let leftColorLiterals;
  301. let rightColorLiterals;
  302. if (!node) {
  303. return [];
  304. }
  305. if (node.type === 'ObjectExpression') {
  306. return [astHelpers.getColorLiteralsFromExpression(node)];
  307. }
  308. switch (node.type) {
  309. case 'LogicalExpression':
  310. leftColorLiterals = astHelpers.getColorLiteralsFromNode(node.left);
  311. rightColorLiterals = astHelpers.getColorLiteralsFromNode(node.right);
  312. return [].concat(leftColorLiterals).concat(rightColorLiterals);
  313. case 'ConditionalExpression':
  314. leftColorLiterals = astHelpers.getColorLiteralsFromNode(node.consequent);
  315. rightColorLiterals = astHelpers.getColorLiteralsFromNode(node.alternate);
  316. return [].concat(leftColorLiterals).concat(rightColorLiterals);
  317. default:
  318. return [];
  319. }
  320. },
  321. hasArrayOfStyleReferences: function (node) {
  322. return node && Boolean(
  323. node.type === 'JSXExpressionContainer'
  324. && node.expression
  325. && node.expression.type === 'ArrayExpression'
  326. );
  327. },
  328. getStyleReferenceFromExpression: function (node) {
  329. const result = [];
  330. const name = astHelpers.getObjectName(node);
  331. if (name) {
  332. result.push(name);
  333. }
  334. const property = astHelpers.getPropertyName(node);
  335. if (property) {
  336. result.push(property);
  337. }
  338. return result.join('.');
  339. },
  340. getStyleObjectFromExpression: function (node) {
  341. const obj = {};
  342. let invalid = false;
  343. if (node.properties && node.properties.length) {
  344. node.properties.forEach((p) => {
  345. if (!p.value || !p.key) {
  346. return;
  347. }
  348. if (p.value.type === 'Literal') {
  349. invalid = true;
  350. obj[p.key.name] = p.value.value;
  351. } else if (p.value.type === 'ConditionalExpression') {
  352. const innerNode = p.value;
  353. if (innerNode.consequent.type === 'Literal' || innerNode.alternate.type === 'Literal') {
  354. invalid = true;
  355. obj[p.key.name] = getSourceCode(innerNode);
  356. }
  357. } else if (p.value.type === 'UnaryExpression' && p.value.operator === '-' && p.value.argument.type === 'Literal') {
  358. invalid = true;
  359. obj[p.key.name] = -1 * p.value.argument.value;
  360. } else if (p.value.type === 'UnaryExpression' && p.value.operator === '+' && p.value.argument.type === 'Literal') {
  361. invalid = true;
  362. obj[p.key.name] = p.value.argument.value;
  363. }
  364. });
  365. }
  366. return invalid ? { expression: obj, node: node } : undefined;
  367. },
  368. getColorLiteralsFromExpression: function (node) {
  369. const obj = {};
  370. let invalid = false;
  371. if (node.properties && node.properties.length) {
  372. node.properties.forEach((p) => {
  373. if (p.key && p.key.name && p.key.name.toLowerCase().indexOf('color') !== -1) {
  374. if (p.value.type === 'Literal') {
  375. invalid = true;
  376. obj[p.key.name] = p.value.value;
  377. } else if (p.value.type === 'ConditionalExpression') {
  378. const innerNode = p.value;
  379. if (innerNode.consequent.type === 'Literal' || innerNode.alternate.type === 'Literal') {
  380. invalid = true;
  381. obj[p.key.name] = getSourceCode(innerNode);
  382. }
  383. }
  384. }
  385. });
  386. }
  387. return invalid ? { expression: obj, node: node } : undefined;
  388. },
  389. getObjectName: function (node) {
  390. if (
  391. node
  392. && node.object
  393. && node.object.name
  394. ) {
  395. return node.object.name;
  396. }
  397. },
  398. getPropertyName: function (node) {
  399. if (
  400. node
  401. && node.property
  402. && node.property.name
  403. ) {
  404. return node.property.name;
  405. }
  406. },
  407. getPotentialStyleReferenceFromMemberExpression: function (node) {
  408. if (
  409. node
  410. && node.object
  411. && node.object.type === 'Identifier'
  412. && node.object.name
  413. && node.property
  414. && node.property.type === 'Identifier'
  415. && node.property.name
  416. && node.parent.type !== 'MemberExpression'
  417. ) {
  418. return [node.object.name, node.property.name].join('.');
  419. }
  420. },
  421. isEitherShortHand: function (property1, property2) {
  422. const shorthands = ['margin', 'padding', 'border', 'flex'];
  423. if (shorthands.includes(property1)) {
  424. return property2.startsWith(property1);
  425. } if (shorthands.includes(property2)) {
  426. return property1.startsWith(property2);
  427. }
  428. return false;
  429. },
  430. };
  431. module.exports.astHelpers = astHelpers;
  432. module.exports.StyleSheets = StyleSheets;