object-parser.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. 'use strict';
  2. const camelCase = require('./camel-case');
  3. const getTemplate = require('./get-template');
  4. const Literal = require('./literal');
  5. const ObjectLiteral = require('./object');
  6. const postcss = require('postcss');
  7. const unCamelCase = require('./un-camel-case');
  8. function forEach(arr, callback) {
  9. arr && arr.forEach(callback);
  10. }
  11. function defineRaws(node, prop, prefix, suffix, props) {
  12. if (!props) {
  13. props = {};
  14. }
  15. const descriptor = {
  16. enumerable: true,
  17. get: () => node[prop],
  18. set: (value) => {
  19. node[prop] = value;
  20. },
  21. };
  22. if (!props.raw) {
  23. props.raw = descriptor;
  24. } else if (props.raw === 'camel') {
  25. props.raw = {
  26. enumerable: true,
  27. get: () => camelCase(node[prop]),
  28. set: (value) => {
  29. node[prop] = unCamelCase(value);
  30. },
  31. };
  32. }
  33. props.value = descriptor;
  34. node.raws[prop] = Object.defineProperties(
  35. {
  36. prefix,
  37. suffix,
  38. },
  39. props,
  40. );
  41. }
  42. class objectParser {
  43. constructor(input) {
  44. this.input = input;
  45. }
  46. parse(node) {
  47. const root = postcss.root({
  48. source: {
  49. input: this.input,
  50. start: node.loc.start,
  51. },
  52. });
  53. root.raws.node = node;
  54. const obj = new ObjectLiteral({
  55. raws: {
  56. node,
  57. },
  58. });
  59. root.push(obj);
  60. this.process(node, obj);
  61. this.sort(root);
  62. this.raws(root);
  63. const startNode = root.first.raws.node;
  64. const endNode = root.last.raws.node;
  65. const start = {
  66. line: startNode.loc.start.line,
  67. };
  68. let before = root.source.input.css.slice(
  69. startNode.start - startNode.loc.start.column,
  70. startNode.start,
  71. );
  72. if (/^\s+$/.test(before)) {
  73. start.column = 1;
  74. } else {
  75. before = '';
  76. start.column = startNode.loc.start.column;
  77. }
  78. root.first.raws.before = before;
  79. root.source.input.css = before + root.source.input.css.slice(startNode.start, endNode.end);
  80. root.source.start = start;
  81. this.root = root;
  82. }
  83. process(node, parent) {
  84. ['leadingComments', 'innerComments', 'trailingComments'].forEach((prop) => {
  85. forEach(node[prop], (child) => {
  86. this.source(child, this.comment(child, parent));
  87. });
  88. });
  89. const child = (this[node.type] || this.literal).apply(this, [node, parent]);
  90. this.source(node, child);
  91. return child;
  92. }
  93. source(node, parent) {
  94. parent.source = {
  95. input: this.input,
  96. start: node.loc.start,
  97. end: node.loc.end,
  98. };
  99. return parent;
  100. }
  101. raws(parent, node) {
  102. const source = this.input.css;
  103. parent.nodes.forEach((child, i) => {
  104. if (i) {
  105. child.raws.before = source
  106. .slice(parent.nodes[i - 1].raws.node.end, child.raws.node.start)
  107. .replace(/^\s*,+/, '');
  108. } else if (node) {
  109. child.raws.before = source.slice(node.start, child.raws.node.start).replace(/^\s*{+/, '');
  110. }
  111. });
  112. if (node) {
  113. let semicolon;
  114. let after;
  115. if (parent.nodes.length) {
  116. after = source.slice(parent.last.raws.node.end, node.end).replace(/^\s*,+/, () => {
  117. semicolon = true;
  118. return '';
  119. });
  120. } else {
  121. after = source.slice(node.start, node.end).replace(/^\s*{/, '');
  122. }
  123. parent.raws.after = after.replace(/}+\s*$/, '');
  124. parent.raws.semicolon = semicolon || false;
  125. }
  126. }
  127. sort(node) {
  128. node.nodes = node.nodes.sort((a, b) => a.raws.node.start - b.raws.node.start);
  129. }
  130. getNodeValue(node, wrappedValue) {
  131. const source = this.input.css;
  132. let rawValue;
  133. let cookedValue;
  134. switch (node.type) {
  135. case 'Identifier': {
  136. const isCssFloat = node.name === 'cssFloat';
  137. return {
  138. prefix: '',
  139. suffix: '',
  140. raw: isCssFloat && node.name,
  141. value: isCssFloat ? 'float' : node.name,
  142. };
  143. }
  144. case 'StringLiteral': {
  145. rawValue = node.extra.raw.slice(1, -1);
  146. cookedValue = node.value;
  147. break;
  148. }
  149. case 'TemplateLiteral': {
  150. rawValue = getTemplate(node, source);
  151. break;
  152. }
  153. default: {
  154. rawValue = source.slice(node.start, node.end);
  155. break;
  156. }
  157. }
  158. const valueWrap = wrappedValue.split(rawValue);
  159. return {
  160. prefix: valueWrap[0],
  161. suffix: valueWrap[1],
  162. value: cookedValue || rawValue,
  163. };
  164. }
  165. ObjectExpression(node, parent) {
  166. forEach(node.properties, (child) => {
  167. this.process(child, parent);
  168. });
  169. this.sort(parent);
  170. this.raws(parent, node);
  171. return parent;
  172. }
  173. ObjectProperty(node, parent) {
  174. const source = this.input.css;
  175. let between = source.indexOf(':', node.key.end);
  176. const rawKey = source.slice(node.start, between).trimRight();
  177. const rawValue = source.slice(between + 1, node.end).trimLeft();
  178. between = source.slice(node.start + rawKey.length, node.end - rawValue.length);
  179. const key = this.getNodeValue(node.key, rawKey);
  180. if (node.value.type === 'ObjectExpression') {
  181. let rule;
  182. if (/^@(\S+)(\s*)(.*)$/.test(key.value)) {
  183. const name = RegExp.$1;
  184. const afterName = RegExp.$2;
  185. const params = RegExp.$3;
  186. const atRule = postcss.atRule({
  187. name: unCamelCase(name),
  188. raws: {
  189. afterName,
  190. },
  191. nodes: [],
  192. });
  193. defineRaws(atRule, 'name', `${key.prefix}@`, params ? '' : key.suffix, {
  194. raw: 'camel',
  195. });
  196. if (params) {
  197. atRule.params = params;
  198. defineRaws(atRule, 'params', '', key.suffix);
  199. }
  200. rule = atRule;
  201. } else {
  202. // rule = this.rule(key, keyWrap, node.value, parent);
  203. rule = postcss.rule({
  204. selector: key.value,
  205. });
  206. defineRaws(rule, 'selector', key.prefix, key.suffix);
  207. }
  208. raw(rule);
  209. this.ObjectExpression(node.value, rule);
  210. return rule;
  211. }
  212. const value = this.getNodeValue(node.value, rawValue);
  213. if (key.value[0] === '@') {
  214. const atRule = postcss.atRule({
  215. name: unCamelCase(key.value),
  216. params: value.value,
  217. });
  218. defineRaws(atRule, 'name', key.prefix, key.suffix, {
  219. raw: 'camel',
  220. });
  221. defineRaws(atRule, 'params', value.prefix, value.suffix);
  222. raw(atRule);
  223. return atRule;
  224. }
  225. let decl;
  226. if (key.raw) {
  227. decl = postcss.decl({
  228. prop: key.value,
  229. value: value.value,
  230. raws: {
  231. prop: key,
  232. },
  233. });
  234. } else {
  235. decl = postcss.decl({
  236. prop: unCamelCase(key.value),
  237. value: value.value,
  238. });
  239. defineRaws(decl, 'prop', key.prefix, key.suffix, {
  240. raw: 'camel',
  241. });
  242. }
  243. defineRaws(decl, 'value', value.prefix, value.suffix);
  244. raw(decl);
  245. return decl;
  246. function raw(postcssNode) {
  247. postcssNode.raws.between = between;
  248. postcssNode.raws.node = node;
  249. parent.push(postcssNode);
  250. }
  251. }
  252. literal(node, parent) {
  253. const literal = new Literal({
  254. text: this.input.css.slice(node.start, node.end),
  255. raws: {
  256. node,
  257. },
  258. });
  259. parent.push(literal);
  260. return literal;
  261. }
  262. comment(node, parent) {
  263. if (
  264. !parent.nodes ||
  265. (node.start < parent.raws.node.start && parent.type !== 'root' && parent.parent)
  266. ) {
  267. return this.comment(node, parent.parent);
  268. }
  269. const text = node.value.match(/^(\s*)((?:\S[\s\S]*?)?)(\s*)$/);
  270. const comment = postcss.comment({
  271. text: text[2],
  272. raws: {
  273. node,
  274. left: text[1],
  275. right: text[3],
  276. inline: node.type === 'CommentLine',
  277. },
  278. });
  279. parent.push(comment);
  280. return comment;
  281. }
  282. }
  283. module.exports = objectParser;