parser.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. (function (factory) {
  2. if (typeof module === "object" && typeof module.exports === "object") {
  3. var v = factory(require, exports);
  4. if (v !== undefined) module.exports = v;
  5. }
  6. else if (typeof define === "function" && define.amd) {
  7. define(["require", "exports", "./scanner"], factory);
  8. }
  9. })(function (require, exports) {
  10. /*---------------------------------------------------------------------------------------------
  11. * Copyright (c) Microsoft Corporation. All rights reserved.
  12. * Licensed under the MIT License. See License.txt in the project root for license information.
  13. *--------------------------------------------------------------------------------------------*/
  14. 'use strict';
  15. Object.defineProperty(exports, "__esModule", { value: true });
  16. exports.getNodeType = exports.stripComments = exports.visit = exports.findNodeAtOffset = exports.contains = exports.getNodeValue = exports.getNodePath = exports.findNodeAtLocation = exports.parseTree = exports.parse = exports.getLocation = void 0;
  17. var scanner_1 = require("./scanner");
  18. var ParseOptions;
  19. (function (ParseOptions) {
  20. ParseOptions.DEFAULT = {
  21. allowTrailingComma: false
  22. };
  23. })(ParseOptions || (ParseOptions = {}));
  24. /**
  25. * For a given offset, evaluate the location in the JSON document. Each segment in the location path is either a property name or an array index.
  26. */
  27. function getLocation(text, position) {
  28. var segments = []; // strings or numbers
  29. var earlyReturnException = new Object();
  30. var previousNode = undefined;
  31. var previousNodeInst = {
  32. value: {},
  33. offset: 0,
  34. length: 0,
  35. type: 'object',
  36. parent: undefined
  37. };
  38. var isAtPropertyKey = false;
  39. function setPreviousNode(value, offset, length, type) {
  40. previousNodeInst.value = value;
  41. previousNodeInst.offset = offset;
  42. previousNodeInst.length = length;
  43. previousNodeInst.type = type;
  44. previousNodeInst.colonOffset = undefined;
  45. previousNode = previousNodeInst;
  46. }
  47. try {
  48. visit(text, {
  49. onObjectBegin: function (offset, length) {
  50. if (position <= offset) {
  51. throw earlyReturnException;
  52. }
  53. previousNode = undefined;
  54. isAtPropertyKey = position > offset;
  55. segments.push(''); // push a placeholder (will be replaced)
  56. },
  57. onObjectProperty: function (name, offset, length) {
  58. if (position < offset) {
  59. throw earlyReturnException;
  60. }
  61. setPreviousNode(name, offset, length, 'property');
  62. segments[segments.length - 1] = name;
  63. if (position <= offset + length) {
  64. throw earlyReturnException;
  65. }
  66. },
  67. onObjectEnd: function (offset, length) {
  68. if (position <= offset) {
  69. throw earlyReturnException;
  70. }
  71. previousNode = undefined;
  72. segments.pop();
  73. },
  74. onArrayBegin: function (offset, length) {
  75. if (position <= offset) {
  76. throw earlyReturnException;
  77. }
  78. previousNode = undefined;
  79. segments.push(0);
  80. },
  81. onArrayEnd: function (offset, length) {
  82. if (position <= offset) {
  83. throw earlyReturnException;
  84. }
  85. previousNode = undefined;
  86. segments.pop();
  87. },
  88. onLiteralValue: function (value, offset, length) {
  89. if (position < offset) {
  90. throw earlyReturnException;
  91. }
  92. setPreviousNode(value, offset, length, getNodeType(value));
  93. if (position <= offset + length) {
  94. throw earlyReturnException;
  95. }
  96. },
  97. onSeparator: function (sep, offset, length) {
  98. if (position <= offset) {
  99. throw earlyReturnException;
  100. }
  101. if (sep === ':' && previousNode && previousNode.type === 'property') {
  102. previousNode.colonOffset = offset;
  103. isAtPropertyKey = false;
  104. previousNode = undefined;
  105. }
  106. else if (sep === ',') {
  107. var last = segments[segments.length - 1];
  108. if (typeof last === 'number') {
  109. segments[segments.length - 1] = last + 1;
  110. }
  111. else {
  112. isAtPropertyKey = true;
  113. segments[segments.length - 1] = '';
  114. }
  115. previousNode = undefined;
  116. }
  117. }
  118. });
  119. }
  120. catch (e) {
  121. if (e !== earlyReturnException) {
  122. throw e;
  123. }
  124. }
  125. return {
  126. path: segments,
  127. previousNode: previousNode,
  128. isAtPropertyKey: isAtPropertyKey,
  129. matches: function (pattern) {
  130. var k = 0;
  131. for (var i = 0; k < pattern.length && i < segments.length; i++) {
  132. if (pattern[k] === segments[i] || pattern[k] === '*') {
  133. k++;
  134. }
  135. else if (pattern[k] !== '**') {
  136. return false;
  137. }
  138. }
  139. return k === pattern.length;
  140. }
  141. };
  142. }
  143. exports.getLocation = getLocation;
  144. /**
  145. * Parses the given text and returns the object the JSON content represents. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result.
  146. * Therefore always check the errors list to find out if the input was valid.
  147. */
  148. function parse(text, errors, options) {
  149. if (errors === void 0) { errors = []; }
  150. if (options === void 0) { options = ParseOptions.DEFAULT; }
  151. var currentProperty = null;
  152. var currentParent = [];
  153. var previousParents = [];
  154. function onValue(value) {
  155. if (Array.isArray(currentParent)) {
  156. currentParent.push(value);
  157. }
  158. else if (currentProperty !== null) {
  159. currentParent[currentProperty] = value;
  160. }
  161. }
  162. var visitor = {
  163. onObjectBegin: function () {
  164. var object = {};
  165. onValue(object);
  166. previousParents.push(currentParent);
  167. currentParent = object;
  168. currentProperty = null;
  169. },
  170. onObjectProperty: function (name) {
  171. currentProperty = name;
  172. },
  173. onObjectEnd: function () {
  174. currentParent = previousParents.pop();
  175. },
  176. onArrayBegin: function () {
  177. var array = [];
  178. onValue(array);
  179. previousParents.push(currentParent);
  180. currentParent = array;
  181. currentProperty = null;
  182. },
  183. onArrayEnd: function () {
  184. currentParent = previousParents.pop();
  185. },
  186. onLiteralValue: onValue,
  187. onError: function (error, offset, length) {
  188. errors.push({ error: error, offset: offset, length: length });
  189. }
  190. };
  191. visit(text, visitor, options);
  192. return currentParent[0];
  193. }
  194. exports.parse = parse;
  195. /**
  196. * Parses the given text and returns a tree representation the JSON content. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result.
  197. */
  198. function parseTree(text, errors, options) {
  199. if (errors === void 0) { errors = []; }
  200. if (options === void 0) { options = ParseOptions.DEFAULT; }
  201. var currentParent = { type: 'array', offset: -1, length: -1, children: [], parent: undefined }; // artificial root
  202. function ensurePropertyComplete(endOffset) {
  203. if (currentParent.type === 'property') {
  204. currentParent.length = endOffset - currentParent.offset;
  205. currentParent = currentParent.parent;
  206. }
  207. }
  208. function onValue(valueNode) {
  209. currentParent.children.push(valueNode);
  210. return valueNode;
  211. }
  212. var visitor = {
  213. onObjectBegin: function (offset) {
  214. currentParent = onValue({ type: 'object', offset: offset, length: -1, parent: currentParent, children: [] });
  215. },
  216. onObjectProperty: function (name, offset, length) {
  217. currentParent = onValue({ type: 'property', offset: offset, length: -1, parent: currentParent, children: [] });
  218. currentParent.children.push({ type: 'string', value: name, offset: offset, length: length, parent: currentParent });
  219. },
  220. onObjectEnd: function (offset, length) {
  221. ensurePropertyComplete(offset + length); // in case of a missing value for a property: make sure property is complete
  222. currentParent.length = offset + length - currentParent.offset;
  223. currentParent = currentParent.parent;
  224. ensurePropertyComplete(offset + length);
  225. },
  226. onArrayBegin: function (offset, length) {
  227. currentParent = onValue({ type: 'array', offset: offset, length: -1, parent: currentParent, children: [] });
  228. },
  229. onArrayEnd: function (offset, length) {
  230. currentParent.length = offset + length - currentParent.offset;
  231. currentParent = currentParent.parent;
  232. ensurePropertyComplete(offset + length);
  233. },
  234. onLiteralValue: function (value, offset, length) {
  235. onValue({ type: getNodeType(value), offset: offset, length: length, parent: currentParent, value: value });
  236. ensurePropertyComplete(offset + length);
  237. },
  238. onSeparator: function (sep, offset, length) {
  239. if (currentParent.type === 'property') {
  240. if (sep === ':') {
  241. currentParent.colonOffset = offset;
  242. }
  243. else if (sep === ',') {
  244. ensurePropertyComplete(offset);
  245. }
  246. }
  247. },
  248. onError: function (error, offset, length) {
  249. errors.push({ error: error, offset: offset, length: length });
  250. }
  251. };
  252. visit(text, visitor, options);
  253. var result = currentParent.children[0];
  254. if (result) {
  255. delete result.parent;
  256. }
  257. return result;
  258. }
  259. exports.parseTree = parseTree;
  260. /**
  261. * Finds the node at the given path in a JSON DOM.
  262. */
  263. function findNodeAtLocation(root, path) {
  264. if (!root) {
  265. return undefined;
  266. }
  267. var node = root;
  268. for (var _i = 0, path_1 = path; _i < path_1.length; _i++) {
  269. var segment = path_1[_i];
  270. if (typeof segment === 'string') {
  271. if (node.type !== 'object' || !Array.isArray(node.children)) {
  272. return undefined;
  273. }
  274. var found = false;
  275. for (var _a = 0, _b = node.children; _a < _b.length; _a++) {
  276. var propertyNode = _b[_a];
  277. if (Array.isArray(propertyNode.children) && propertyNode.children[0].value === segment) {
  278. node = propertyNode.children[1];
  279. found = true;
  280. break;
  281. }
  282. }
  283. if (!found) {
  284. return undefined;
  285. }
  286. }
  287. else {
  288. var index = segment;
  289. if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) {
  290. return undefined;
  291. }
  292. node = node.children[index];
  293. }
  294. }
  295. return node;
  296. }
  297. exports.findNodeAtLocation = findNodeAtLocation;
  298. /**
  299. * Gets the JSON path of the given JSON DOM node
  300. */
  301. function getNodePath(node) {
  302. if (!node.parent || !node.parent.children) {
  303. return [];
  304. }
  305. var path = getNodePath(node.parent);
  306. if (node.parent.type === 'property') {
  307. var key = node.parent.children[0].value;
  308. path.push(key);
  309. }
  310. else if (node.parent.type === 'array') {
  311. var index = node.parent.children.indexOf(node);
  312. if (index !== -1) {
  313. path.push(index);
  314. }
  315. }
  316. return path;
  317. }
  318. exports.getNodePath = getNodePath;
  319. /**
  320. * Evaluates the JavaScript object of the given JSON DOM node
  321. */
  322. function getNodeValue(node) {
  323. switch (node.type) {
  324. case 'array':
  325. return node.children.map(getNodeValue);
  326. case 'object':
  327. var obj = Object.create(null);
  328. for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
  329. var prop = _a[_i];
  330. var valueNode = prop.children[1];
  331. if (valueNode) {
  332. obj[prop.children[0].value] = getNodeValue(valueNode);
  333. }
  334. }
  335. return obj;
  336. case 'null':
  337. case 'string':
  338. case 'number':
  339. case 'boolean':
  340. return node.value;
  341. default:
  342. return undefined;
  343. }
  344. }
  345. exports.getNodeValue = getNodeValue;
  346. function contains(node, offset, includeRightBound) {
  347. if (includeRightBound === void 0) { includeRightBound = false; }
  348. return (offset >= node.offset && offset < (node.offset + node.length)) || includeRightBound && (offset === (node.offset + node.length));
  349. }
  350. exports.contains = contains;
  351. /**
  352. * Finds the most inner node at the given offset. If includeRightBound is set, also finds nodes that end at the given offset.
  353. */
  354. function findNodeAtOffset(node, offset, includeRightBound) {
  355. if (includeRightBound === void 0) { includeRightBound = false; }
  356. if (contains(node, offset, includeRightBound)) {
  357. var children = node.children;
  358. if (Array.isArray(children)) {
  359. for (var i = 0; i < children.length && children[i].offset <= offset; i++) {
  360. var item = findNodeAtOffset(children[i], offset, includeRightBound);
  361. if (item) {
  362. return item;
  363. }
  364. }
  365. }
  366. return node;
  367. }
  368. return undefined;
  369. }
  370. exports.findNodeAtOffset = findNodeAtOffset;
  371. /**
  372. * Parses the given text and invokes the visitor functions for each object, array and literal reached.
  373. */
  374. function visit(text, visitor, options) {
  375. if (options === void 0) { options = ParseOptions.DEFAULT; }
  376. var _scanner = scanner_1.createScanner(text, false);
  377. function toNoArgVisit(visitFunction) {
  378. return visitFunction ? function () { return visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()); } : function () { return true; };
  379. }
  380. function toOneArgVisit(visitFunction) {
  381. return visitFunction ? function (arg) { return visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()); } : function () { return true; };
  382. }
  383. var onObjectBegin = toNoArgVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisit(visitor.onObjectProperty), onObjectEnd = toNoArgVisit(visitor.onObjectEnd), onArrayBegin = toNoArgVisit(visitor.onArrayBegin), onArrayEnd = toNoArgVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisit(visitor.onLiteralValue), onSeparator = toOneArgVisit(visitor.onSeparator), onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError);
  384. var disallowComments = options && options.disallowComments;
  385. var allowTrailingComma = options && options.allowTrailingComma;
  386. function scanNext() {
  387. while (true) {
  388. var token = _scanner.scan();
  389. switch (_scanner.getTokenError()) {
  390. case 4 /* InvalidUnicode */:
  391. handleError(14 /* InvalidUnicode */);
  392. break;
  393. case 5 /* InvalidEscapeCharacter */:
  394. handleError(15 /* InvalidEscapeCharacter */);
  395. break;
  396. case 3 /* UnexpectedEndOfNumber */:
  397. handleError(13 /* UnexpectedEndOfNumber */);
  398. break;
  399. case 1 /* UnexpectedEndOfComment */:
  400. if (!disallowComments) {
  401. handleError(11 /* UnexpectedEndOfComment */);
  402. }
  403. break;
  404. case 2 /* UnexpectedEndOfString */:
  405. handleError(12 /* UnexpectedEndOfString */);
  406. break;
  407. case 6 /* InvalidCharacter */:
  408. handleError(16 /* InvalidCharacter */);
  409. break;
  410. }
  411. switch (token) {
  412. case 12 /* LineCommentTrivia */:
  413. case 13 /* BlockCommentTrivia */:
  414. if (disallowComments) {
  415. handleError(10 /* InvalidCommentToken */);
  416. }
  417. else {
  418. onComment();
  419. }
  420. break;
  421. case 16 /* Unknown */:
  422. handleError(1 /* InvalidSymbol */);
  423. break;
  424. case 15 /* Trivia */:
  425. case 14 /* LineBreakTrivia */:
  426. break;
  427. default:
  428. return token;
  429. }
  430. }
  431. }
  432. function handleError(error, skipUntilAfter, skipUntil) {
  433. if (skipUntilAfter === void 0) { skipUntilAfter = []; }
  434. if (skipUntil === void 0) { skipUntil = []; }
  435. onError(error);
  436. if (skipUntilAfter.length + skipUntil.length > 0) {
  437. var token = _scanner.getToken();
  438. while (token !== 17 /* EOF */) {
  439. if (skipUntilAfter.indexOf(token) !== -1) {
  440. scanNext();
  441. break;
  442. }
  443. else if (skipUntil.indexOf(token) !== -1) {
  444. break;
  445. }
  446. token = scanNext();
  447. }
  448. }
  449. }
  450. function parseString(isValue) {
  451. var value = _scanner.getTokenValue();
  452. if (isValue) {
  453. onLiteralValue(value);
  454. }
  455. else {
  456. onObjectProperty(value);
  457. }
  458. scanNext();
  459. return true;
  460. }
  461. function parseLiteral() {
  462. switch (_scanner.getToken()) {
  463. case 11 /* NumericLiteral */:
  464. var tokenValue = _scanner.getTokenValue();
  465. var value = Number(tokenValue);
  466. if (isNaN(value)) {
  467. handleError(2 /* InvalidNumberFormat */);
  468. value = 0;
  469. }
  470. onLiteralValue(value);
  471. break;
  472. case 7 /* NullKeyword */:
  473. onLiteralValue(null);
  474. break;
  475. case 8 /* TrueKeyword */:
  476. onLiteralValue(true);
  477. break;
  478. case 9 /* FalseKeyword */:
  479. onLiteralValue(false);
  480. break;
  481. default:
  482. return false;
  483. }
  484. scanNext();
  485. return true;
  486. }
  487. function parseProperty() {
  488. if (_scanner.getToken() !== 10 /* StringLiteral */) {
  489. handleError(3 /* PropertyNameExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
  490. return false;
  491. }
  492. parseString(false);
  493. if (_scanner.getToken() === 6 /* ColonToken */) {
  494. onSeparator(':');
  495. scanNext(); // consume colon
  496. if (!parseValue()) {
  497. handleError(4 /* ValueExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
  498. }
  499. }
  500. else {
  501. handleError(5 /* ColonExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
  502. }
  503. return true;
  504. }
  505. function parseObject() {
  506. onObjectBegin();
  507. scanNext(); // consume open brace
  508. var needsComma = false;
  509. while (_scanner.getToken() !== 2 /* CloseBraceToken */ && _scanner.getToken() !== 17 /* EOF */) {
  510. if (_scanner.getToken() === 5 /* CommaToken */) {
  511. if (!needsComma) {
  512. handleError(4 /* ValueExpected */, [], []);
  513. }
  514. onSeparator(',');
  515. scanNext(); // consume comma
  516. if (_scanner.getToken() === 2 /* CloseBraceToken */ && allowTrailingComma) {
  517. break;
  518. }
  519. }
  520. else if (needsComma) {
  521. handleError(6 /* CommaExpected */, [], []);
  522. }
  523. if (!parseProperty()) {
  524. handleError(4 /* ValueExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
  525. }
  526. needsComma = true;
  527. }
  528. onObjectEnd();
  529. if (_scanner.getToken() !== 2 /* CloseBraceToken */) {
  530. handleError(7 /* CloseBraceExpected */, [2 /* CloseBraceToken */], []);
  531. }
  532. else {
  533. scanNext(); // consume close brace
  534. }
  535. return true;
  536. }
  537. function parseArray() {
  538. onArrayBegin();
  539. scanNext(); // consume open bracket
  540. var needsComma = false;
  541. while (_scanner.getToken() !== 4 /* CloseBracketToken */ && _scanner.getToken() !== 17 /* EOF */) {
  542. if (_scanner.getToken() === 5 /* CommaToken */) {
  543. if (!needsComma) {
  544. handleError(4 /* ValueExpected */, [], []);
  545. }
  546. onSeparator(',');
  547. scanNext(); // consume comma
  548. if (_scanner.getToken() === 4 /* CloseBracketToken */ && allowTrailingComma) {
  549. break;
  550. }
  551. }
  552. else if (needsComma) {
  553. handleError(6 /* CommaExpected */, [], []);
  554. }
  555. if (!parseValue()) {
  556. handleError(4 /* ValueExpected */, [], [4 /* CloseBracketToken */, 5 /* CommaToken */]);
  557. }
  558. needsComma = true;
  559. }
  560. onArrayEnd();
  561. if (_scanner.getToken() !== 4 /* CloseBracketToken */) {
  562. handleError(8 /* CloseBracketExpected */, [4 /* CloseBracketToken */], []);
  563. }
  564. else {
  565. scanNext(); // consume close bracket
  566. }
  567. return true;
  568. }
  569. function parseValue() {
  570. switch (_scanner.getToken()) {
  571. case 3 /* OpenBracketToken */:
  572. return parseArray();
  573. case 1 /* OpenBraceToken */:
  574. return parseObject();
  575. case 10 /* StringLiteral */:
  576. return parseString(true);
  577. default:
  578. return parseLiteral();
  579. }
  580. }
  581. scanNext();
  582. if (_scanner.getToken() === 17 /* EOF */) {
  583. if (options.allowEmptyContent) {
  584. return true;
  585. }
  586. handleError(4 /* ValueExpected */, [], []);
  587. return false;
  588. }
  589. if (!parseValue()) {
  590. handleError(4 /* ValueExpected */, [], []);
  591. return false;
  592. }
  593. if (_scanner.getToken() !== 17 /* EOF */) {
  594. handleError(9 /* EndOfFileExpected */, [], []);
  595. }
  596. return true;
  597. }
  598. exports.visit = visit;
  599. /**
  600. * Takes JSON with JavaScript-style comments and remove
  601. * them. Optionally replaces every none-newline character
  602. * of comments with a replaceCharacter
  603. */
  604. function stripComments(text, replaceCh) {
  605. var _scanner = scanner_1.createScanner(text), parts = [], kind, offset = 0, pos;
  606. do {
  607. pos = _scanner.getPosition();
  608. kind = _scanner.scan();
  609. switch (kind) {
  610. case 12 /* LineCommentTrivia */:
  611. case 13 /* BlockCommentTrivia */:
  612. case 17 /* EOF */:
  613. if (offset !== pos) {
  614. parts.push(text.substring(offset, pos));
  615. }
  616. if (replaceCh !== undefined) {
  617. parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
  618. }
  619. offset = _scanner.getPosition();
  620. break;
  621. }
  622. } while (kind !== 17 /* EOF */);
  623. return parts.join('');
  624. }
  625. exports.stripComments = stripComments;
  626. function getNodeType(value) {
  627. switch (typeof value) {
  628. case 'boolean': return 'boolean';
  629. case 'number': return 'number';
  630. case 'string': return 'string';
  631. case 'object': {
  632. if (!value) {
  633. return 'null';
  634. }
  635. else if (Array.isArray(value)) {
  636. return 'array';
  637. }
  638. return 'object';
  639. }
  640. default: return 'null';
  641. }
  642. }
  643. exports.getNodeType = getNodeType;
  644. });