Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. 'use strict';
  2. const XHTMLEntities = require('./xhtml');
  3. const hexNumber = /^[\da-fA-F]+$/;
  4. const decimalNumber = /^\d+$/;
  5. const acorn = require("acorn");
  6. const tt = acorn.tokTypes;
  7. const TokContext = acorn.TokContext;
  8. const tokContexts = acorn.tokContexts;
  9. const TokenType = acorn.TokenType;
  10. const isNewLine = acorn.isNewLine;
  11. const isIdentifierStart = acorn.isIdentifierStart;
  12. const isIdentifierChar = acorn.isIdentifierChar;
  13. const tc_oTag = new TokContext('<tag', false);
  14. const tc_cTag = new TokContext('</tag', false);
  15. const tc_expr = new TokContext('<tag>...</tag>', true, true);
  16. const tok = {
  17. jsxName: new TokenType('jsxName'),
  18. jsxText: new TokenType('jsxText', {beforeExpr: true}),
  19. jsxTagStart: new TokenType('jsxTagStart'),
  20. jsxTagEnd: new TokenType('jsxTagEnd')
  21. }
  22. tok.jsxTagStart.updateContext = function() {
  23. this.context.push(tc_expr); // treat as beginning of JSX expression
  24. this.context.push(tc_oTag); // start opening tag context
  25. this.exprAllowed = false;
  26. };
  27. tok.jsxTagEnd.updateContext = function(prevType) {
  28. let out = this.context.pop();
  29. if (out === tc_oTag && prevType === tt.slash || out === tc_cTag) {
  30. this.context.pop();
  31. this.exprAllowed = this.curContext() === tc_expr;
  32. } else {
  33. this.exprAllowed = true;
  34. }
  35. };
  36. // Transforms JSX element name to string.
  37. function getQualifiedJSXName(object) {
  38. if (!object)
  39. return object;
  40. if (object.type === 'JSXIdentifier')
  41. return object.name;
  42. if (object.type === 'JSXNamespacedName')
  43. return object.namespace.name + ':' + object.name.name;
  44. if (object.type === 'JSXMemberExpression')
  45. return getQualifiedJSXName(object.object) + '.' +
  46. getQualifiedJSXName(object.property);
  47. }
  48. module.exports = function(options) {
  49. options = options || {};
  50. return function(Parser) {
  51. return plugin({
  52. allowNamespaces: options.allowNamespaces !== false,
  53. allowNamespacedObjects: !!options.allowNamespacedObjects
  54. }, Parser);
  55. }
  56. };
  57. module.exports.tokTypes = tok;
  58. function plugin(options, Parser) {
  59. return class extends Parser {
  60. // Reads inline JSX contents token.
  61. jsx_readToken() {
  62. let out = '', chunkStart = this.pos;
  63. for (;;) {
  64. if (this.pos >= this.input.length)
  65. this.raise(this.start, 'Unterminated JSX contents');
  66. let ch = this.input.charCodeAt(this.pos);
  67. switch (ch) {
  68. case 60: // '<'
  69. case 123: // '{'
  70. if (this.pos === this.start) {
  71. if (ch === 60 && this.exprAllowed) {
  72. ++this.pos;
  73. return this.finishToken(tok.jsxTagStart);
  74. }
  75. return this.getTokenFromCode(ch);
  76. }
  77. out += this.input.slice(chunkStart, this.pos);
  78. return this.finishToken(tok.jsxText, out);
  79. case 38: // '&'
  80. out += this.input.slice(chunkStart, this.pos);
  81. out += this.jsx_readEntity();
  82. chunkStart = this.pos;
  83. break;
  84. default:
  85. if (isNewLine(ch)) {
  86. out += this.input.slice(chunkStart, this.pos);
  87. out += this.jsx_readNewLine(true);
  88. chunkStart = this.pos;
  89. } else {
  90. ++this.pos;
  91. }
  92. }
  93. }
  94. }
  95. jsx_readNewLine(normalizeCRLF) {
  96. let ch = this.input.charCodeAt(this.pos);
  97. let out;
  98. ++this.pos;
  99. if (ch === 13 && this.input.charCodeAt(this.pos) === 10) {
  100. ++this.pos;
  101. out = normalizeCRLF ? '\n' : '\r\n';
  102. } else {
  103. out = String.fromCharCode(ch);
  104. }
  105. if (this.options.locations) {
  106. ++this.curLine;
  107. this.lineStart = this.pos;
  108. }
  109. return out;
  110. }
  111. jsx_readString(quote) {
  112. let out = '', chunkStart = ++this.pos;
  113. for (;;) {
  114. if (this.pos >= this.input.length)
  115. this.raise(this.start, 'Unterminated string constant');
  116. let ch = this.input.charCodeAt(this.pos);
  117. if (ch === quote) break;
  118. if (ch === 38) { // '&'
  119. out += this.input.slice(chunkStart, this.pos);
  120. out += this.jsx_readEntity();
  121. chunkStart = this.pos;
  122. } else if (isNewLine(ch)) {
  123. out += this.input.slice(chunkStart, this.pos);
  124. out += this.jsx_readNewLine(false);
  125. chunkStart = this.pos;
  126. } else {
  127. ++this.pos;
  128. }
  129. }
  130. out += this.input.slice(chunkStart, this.pos++);
  131. return this.finishToken(tt.string, out);
  132. }
  133. jsx_readEntity() {
  134. let str = '', count = 0, entity;
  135. let ch = this.input[this.pos];
  136. if (ch !== '&')
  137. this.raise(this.pos, 'Entity must start with an ampersand');
  138. let startPos = ++this.pos;
  139. while (this.pos < this.input.length && count++ < 10) {
  140. ch = this.input[this.pos++];
  141. if (ch === ';') {
  142. if (str[0] === '#') {
  143. if (str[1] === 'x') {
  144. str = str.substr(2);
  145. if (hexNumber.test(str))
  146. entity = String.fromCharCode(parseInt(str, 16));
  147. } else {
  148. str = str.substr(1);
  149. if (decimalNumber.test(str))
  150. entity = String.fromCharCode(parseInt(str, 10));
  151. }
  152. } else {
  153. entity = XHTMLEntities[str];
  154. }
  155. break;
  156. }
  157. str += ch;
  158. }
  159. if (!entity) {
  160. this.pos = startPos;
  161. return '&';
  162. }
  163. return entity;
  164. }
  165. // Read a JSX identifier (valid tag or attribute name).
  166. //
  167. // Optimized version since JSX identifiers can't contain
  168. // escape characters and so can be read as single slice.
  169. // Also assumes that first character was already checked
  170. // by isIdentifierStart in readToken.
  171. jsx_readWord() {
  172. let ch, start = this.pos;
  173. do {
  174. ch = this.input.charCodeAt(++this.pos);
  175. } while (isIdentifierChar(ch) || ch === 45); // '-'
  176. return this.finishToken(tok.jsxName, this.input.slice(start, this.pos));
  177. }
  178. // Parse next token as JSX identifier
  179. jsx_parseIdentifier() {
  180. let node = this.startNode();
  181. if (this.type === tok.jsxName)
  182. node.name = this.value;
  183. else if (this.type.keyword)
  184. node.name = this.type.keyword;
  185. else
  186. this.unexpected();
  187. this.next();
  188. return this.finishNode(node, 'JSXIdentifier');
  189. }
  190. // Parse namespaced identifier.
  191. jsx_parseNamespacedName() {
  192. let startPos = this.start, startLoc = this.startLoc;
  193. let name = this.jsx_parseIdentifier();
  194. if (!options.allowNamespaces || !this.eat(tt.colon)) return name;
  195. var node = this.startNodeAt(startPos, startLoc);
  196. node.namespace = name;
  197. node.name = this.jsx_parseIdentifier();
  198. return this.finishNode(node, 'JSXNamespacedName');
  199. }
  200. // Parses element name in any form - namespaced, member
  201. // or single identifier.
  202. jsx_parseElementName() {
  203. if (this.type === tok.jsxTagEnd) return '';
  204. let startPos = this.start, startLoc = this.startLoc;
  205. let node = this.jsx_parseNamespacedName();
  206. if (this.type === tt.dot && node.type === 'JSXNamespacedName' && !options.allowNamespacedObjects) {
  207. this.unexpected();
  208. }
  209. while (this.eat(tt.dot)) {
  210. let newNode = this.startNodeAt(startPos, startLoc);
  211. newNode.object = node;
  212. newNode.property = this.jsx_parseIdentifier();
  213. node = this.finishNode(newNode, 'JSXMemberExpression');
  214. }
  215. return node;
  216. }
  217. // Parses any type of JSX attribute value.
  218. jsx_parseAttributeValue() {
  219. switch (this.type) {
  220. case tt.braceL:
  221. let node = this.jsx_parseExpressionContainer();
  222. if (node.expression.type === 'JSXEmptyExpression')
  223. this.raise(node.start, 'JSX attributes must only be assigned a non-empty expression');
  224. return node;
  225. case tok.jsxTagStart:
  226. case tt.string:
  227. return this.parseExprAtom();
  228. default:
  229. this.raise(this.start, 'JSX value should be either an expression or a quoted JSX text');
  230. }
  231. }
  232. // JSXEmptyExpression is unique type since it doesn't actually parse anything,
  233. // and so it should start at the end of last read token (left brace) and finish
  234. // at the beginning of the next one (right brace).
  235. jsx_parseEmptyExpression() {
  236. let node = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc);
  237. return this.finishNodeAt(node, 'JSXEmptyExpression', this.start, this.startLoc);
  238. }
  239. // Parses JSX expression enclosed into curly brackets.
  240. jsx_parseExpressionContainer() {
  241. let node = this.startNode();
  242. this.next();
  243. node.expression = this.type === tt.braceR
  244. ? this.jsx_parseEmptyExpression()
  245. : this.parseExpression();
  246. this.expect(tt.braceR);
  247. return this.finishNode(node, 'JSXExpressionContainer');
  248. }
  249. // Parses following JSX attribute name-value pair.
  250. jsx_parseAttribute() {
  251. let node = this.startNode();
  252. if (this.eat(tt.braceL)) {
  253. this.expect(tt.ellipsis);
  254. node.argument = this.parseMaybeAssign();
  255. this.expect(tt.braceR);
  256. return this.finishNode(node, 'JSXSpreadAttribute');
  257. }
  258. node.name = this.jsx_parseNamespacedName();
  259. node.value = this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null;
  260. return this.finishNode(node, 'JSXAttribute');
  261. }
  262. // Parses JSX opening tag starting after '<'.
  263. jsx_parseOpeningElementAt(startPos, startLoc) {
  264. let node = this.startNodeAt(startPos, startLoc);
  265. node.attributes = [];
  266. let nodeName = this.jsx_parseElementName();
  267. if (nodeName) node.name = nodeName;
  268. while (this.type !== tt.slash && this.type !== tok.jsxTagEnd)
  269. node.attributes.push(this.jsx_parseAttribute());
  270. node.selfClosing = this.eat(tt.slash);
  271. this.expect(tok.jsxTagEnd);
  272. return this.finishNode(node, nodeName ? 'JSXOpeningElement' : 'JSXOpeningFragment');
  273. }
  274. // Parses JSX closing tag starting after '</'.
  275. jsx_parseClosingElementAt(startPos, startLoc) {
  276. let node = this.startNodeAt(startPos, startLoc);
  277. let nodeName = this.jsx_parseElementName();
  278. if (nodeName) node.name = nodeName;
  279. this.expect(tok.jsxTagEnd);
  280. return this.finishNode(node, nodeName ? 'JSXClosingElement' : 'JSXClosingFragment');
  281. }
  282. // Parses entire JSX element, including it's opening tag
  283. // (starting after '<'), attributes, contents and closing tag.
  284. jsx_parseElementAt(startPos, startLoc) {
  285. let node = this.startNodeAt(startPos, startLoc);
  286. let children = [];
  287. let openingElement = this.jsx_parseOpeningElementAt(startPos, startLoc);
  288. let closingElement = null;
  289. if (!openingElement.selfClosing) {
  290. contents: for (;;) {
  291. switch (this.type) {
  292. case tok.jsxTagStart:
  293. startPos = this.start; startLoc = this.startLoc;
  294. this.next();
  295. if (this.eat(tt.slash)) {
  296. closingElement = this.jsx_parseClosingElementAt(startPos, startLoc);
  297. break contents;
  298. }
  299. children.push(this.jsx_parseElementAt(startPos, startLoc));
  300. break;
  301. case tok.jsxText:
  302. children.push(this.parseExprAtom());
  303. break;
  304. case tt.braceL:
  305. children.push(this.jsx_parseExpressionContainer());
  306. break;
  307. default:
  308. this.unexpected();
  309. }
  310. }
  311. if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
  312. this.raise(
  313. closingElement.start,
  314. 'Expected corresponding JSX closing tag for <' + getQualifiedJSXName(openingElement.name) + '>');
  315. }
  316. }
  317. let fragmentOrElement = openingElement.name ? 'Element' : 'Fragment';
  318. node['opening' + fragmentOrElement] = openingElement;
  319. node['closing' + fragmentOrElement] = closingElement;
  320. node.children = children;
  321. if (this.type === tt.relational && this.value === "<") {
  322. this.raise(this.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
  323. }
  324. return this.finishNode(node, 'JSX' + fragmentOrElement);
  325. }
  326. // Parse JSX text
  327. jsx_parseText(value) {
  328. let node = this.parseLiteral(value);
  329. node.type = "JSXText";
  330. return node;
  331. }
  332. // Parses entire JSX element from current position.
  333. jsx_parseElement() {
  334. let startPos = this.start, startLoc = this.startLoc;
  335. this.next();
  336. return this.jsx_parseElementAt(startPos, startLoc);
  337. }
  338. parseExprAtom(refShortHandDefaultPos) {
  339. if (this.type === tok.jsxText)
  340. return this.jsx_parseText(this.value);
  341. else if (this.type === tok.jsxTagStart)
  342. return this.jsx_parseElement();
  343. else
  344. return super.parseExprAtom(refShortHandDefaultPos);
  345. }
  346. readToken(code) {
  347. let context = this.curContext();
  348. if (context === tc_expr) return this.jsx_readToken();
  349. if (context === tc_oTag || context === tc_cTag) {
  350. if (isIdentifierStart(code)) return this.jsx_readWord();
  351. if (code == 62) {
  352. ++this.pos;
  353. return this.finishToken(tok.jsxTagEnd);
  354. }
  355. if ((code === 34 || code === 39) && context == tc_oTag)
  356. return this.jsx_readString(code);
  357. }
  358. if (code === 60 && this.exprAllowed && this.input.charCodeAt(this.pos + 1) !== 33) {
  359. ++this.pos;
  360. return this.finishToken(tok.jsxTagStart);
  361. }
  362. return super.readToken(code)
  363. }
  364. updateContext(prevType) {
  365. if (this.type == tt.braceL) {
  366. var curContext = this.curContext();
  367. if (curContext == tc_oTag) this.context.push(tokContexts.b_expr);
  368. else if (curContext == tc_expr) this.context.push(tokContexts.b_tmpl);
  369. else super.updateContext(prevType)
  370. this.exprAllowed = true;
  371. } else if (this.type === tt.slash && prevType === tok.jsxTagStart) {
  372. this.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
  373. this.context.push(tc_cTag); // reconsider as closing tag context
  374. this.exprAllowed = false;
  375. } else {
  376. return super.updateContext(prevType);
  377. }
  378. }
  379. };
  380. }