| // Copyright 2016 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| const RelaxedJSONParser = { | |
| /** | |
| * @param {string} content | |
| * @return {*} | |
| */ | |
| parse(content) { | |
| content = '(' + content + ')'; | |
| let root; | |
| try { | |
| root = acorn.parse(content, {}); | |
| } catch (e) { | |
| return null; | |
| } | |
| const walker = new FormatterWorker.ESTreeWalker(beforeVisit, afterVisit); | |
| const rootTip = []; | |
| /** @type {!Array.<!FormatterWorker.RelaxedJSONParser.Context>} */ | |
| const stack = []; | |
| let stackData = /** @type {!FormatterWorker.RelaxedJSONParser.Context} */ ( | |
| {key: 0, tip: rootTip, state: States.ExpectValue, parentIsArray: true}); | |
| walker.setWalkNulls(true); | |
| let hasExpression = false; | |
| walker.walk(root); | |
| if (hasExpression) { | |
| return null; | |
| } | |
| return rootTip.length ? rootTip[0] : null; | |
| /** | |
| * @param {!FormatterWorker.RelaxedJSONParser.Context} newStack | |
| */ | |
| function pushStack(newStack) { | |
| stack.push(stackData); | |
| stackData = newStack; | |
| } | |
| function popStack() { | |
| stackData = stack.pop(); | |
| } | |
| /** | |
| * @param {*} value | |
| */ | |
| function applyValue(value) { | |
| stackData.tip[stackData.key] = value; | |
| if (stackData.parentIsArray) { | |
| stackData.key++; | |
| } else { | |
| stackData.state = null; | |
| } | |
| } | |
| /** | |
| * @param {!ESTree.Node} node | |
| * @return {!Object|undefined} | |
| */ | |
| function beforeVisit(node) { | |
| switch (node.type) { | |
| case 'ObjectExpression': { | |
| const newTip = {}; | |
| applyValue(newTip); | |
| pushStack(/** @type {!FormatterWorker.RelaxedJSONParser.Context} */ ( | |
| {key: null, tip: newTip, state: null, parentIsArray: false})); | |
| break; | |
| } | |
| case 'ArrayExpression': { | |
| const newTip = []; | |
| applyValue(newTip); | |
| pushStack(/** @type {!FormatterWorker.RelaxedJSONParser.Context} */ ( | |
| {key: 0, tip: newTip, state: States.ExpectValue, parentIsArray: true})); | |
| break; | |
| } | |
| case 'Property': | |
| stackData.state = States.ExpectKey; | |
| break; | |
| case 'Literal': | |
| if (stackData.state === States.ExpectKey) { | |
| stackData.key = node.value; | |
| stackData.state = States.ExpectValue; | |
| } else if (stackData.state === States.ExpectValue) { | |
| applyValue(extractValue(node)); | |
| return FormatterWorker.ESTreeWalker.SkipSubtree; | |
| } | |
| break; | |
| case 'Identifier': | |
| if (stackData.state === States.ExpectKey) { | |
| stackData.key = /** @type {string} */ (node.name); | |
| stackData.state = States.ExpectValue; | |
| } else if (stackData.state === States.ExpectValue) { | |
| applyValue(extractValue(node)); | |
| return FormatterWorker.ESTreeWalker.SkipSubtree; | |
| } | |
| break; | |
| case 'UnaryExpression': | |
| if (stackData.state === States.ExpectValue) { | |
| applyValue(extractValue(node)); | |
| return FormatterWorker.ESTreeWalker.SkipSubtree; | |
| } | |
| break; | |
| case 'Program': | |
| case 'ExpressionStatement': | |
| break; | |
| default: | |
| if (stackData.state === States.ExpectValue) { | |
| applyValue(extractValue(node)); | |
| } | |
| return FormatterWorker.ESTreeWalker.SkipSubtree; | |
| } | |
| } | |
| /** | |
| * @param {!ESTree.Node} node | |
| */ | |
| function afterVisit(node) { | |
| if (node.type === 'ObjectExpression' || node.type === 'ArrayExpression') { | |
| popStack(); | |
| } | |
| } | |
| /** | |
| * @param {!ESTree.Node} node | |
| * @return {*} | |
| */ | |
| function extractValue(node) { | |
| let isNegative = false; | |
| const originalNode = node; | |
| let value; | |
| if (node.type === 'UnaryExpression' && (node.operator === '-' || node.operator === '+')) { | |
| if (node.operator === '-') { | |
| isNegative = true; | |
| } | |
| node = /** @type {!ESTree.Node} */ (node.argument); | |
| } | |
| if (node.type === 'Literal') { | |
| value = node.value; | |
| } else if (node.type === 'Identifier' && Keywords.hasOwnProperty(node.name)) { | |
| value = Keywords[node.name]; | |
| } else { | |
| hasExpression = true; | |
| return content.substring(originalNode.start, originalNode.end); | |
| } | |
| if (isNegative) { | |
| if (typeof value !== 'number') { | |
| hasExpression = true; | |
| return content.substring(originalNode.start, originalNode.end); | |
| } | |
| value = -(value); | |
| } | |
| return value; | |
| } | |
| } | |
| }; | |
| export default RelaxedJSONParser; | |
| /** @enum {string} */ | |
| const States = { | |
| ExpectKey: 'ExpectKey', | |
| ExpectValue: 'ExpectValue' | |
| }; | |
| /** @enum {*} */ | |
| const Keywords = { | |
| 'NaN': NaN, | |
| 'true': true, | |
| 'false': false, | |
| 'Infinity': Infinity, | |
| 'undefined': undefined, | |
| 'null': null | |
| }; | |
| /* Legacy exported object */ | |
| self.FormatterWorker = self.FormatterWorker || {}; | |
| /* Legacy exported object */ | |
| FormatterWorker = FormatterWorker || {}; | |
| FormatterWorker.RelaxedJSONParser = RelaxedJSONParser; | |
| /** | |
| * @typedef {!{key: (number|string), tip: (!Array|!Object), state: ?States, parentIsArray: boolean}} | |
| */ | |
| FormatterWorker.RelaxedJSONParser.Context; |