Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

decaffeinate-parser

Package Overview
Dependencies
Maintainers
1
Versions
156
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

decaffeinate-parser - npm Package Compare versions

Comparing version 1.0.2 to 1.1.0

src/ext/coffee-script.js

23

package.json
{
"name": "decaffeinate-parser",
"version": "1.0.2",
"version": "1.1.0",
"description": "A better AST for CoffeeScript, inspired by CoffeeScriptRedux.",
"main": "lib/parser.js",
"jsnext:main": "src/parser.js",
"main": "dist/decaffeinate-parser.umd.js",
"jsnext:main": "dist/decaffeinate-parser.es6.js",
"scripts": {
"prepublish": "babel --stage 0 src -d lib",
"test": "mocha --compilers js:babel/register"
"build": "rollup -c rollup.config.es6.js && rollup -c rollup.config.umd.js",
"prepublish": "npm run build",
"test": "mocha --compilers js:babel-register"
},

@@ -26,5 +27,11 @@ "keywords": [

"devDependencies": {
"babel": "^5.8.23",
"babel": "^6.3.26",
"babel-preset-es2015": "^6.3.13",
"babel-preset-es2015-rollup": "^1.1.1",
"babel-register": "^6.4.3",
"binary-search": "^1.2.0",
"coffee-script-redux": "^2.0.0-beta8",
"mocha": "^2.3.3",
"mocha": "^2.3.4",
"rollup": "^0.25.0",
"rollup-plugin-babel": "^2.3.9",
"string-repeat": "^1.1.1"

@@ -35,2 +42,2 @@ },

}
}
}

@@ -16,4 +16,8 @@ # decaffeinate-parser

* Single-line functions, `if` statements, etc. have blocks for bodies.
* String interpolation nodes are modeled after `TemplateLiteral` from ES6 rather
than being a series of nested `ConcatOp` nodes.
* Triple-quoted strings have the node type `Herestring` rather than `String`.
* Virtual nodes (such as the `LogicalNotOp` generated by an `unless`) are
marked as such with a `virtual: true` property.
* `do` is handled with the `DoOp` node type.
* `for-in` loops do not have an implicit `step` property.

@@ -20,0 +24,0 @@ * Ranges of programs with indented blocks are correct.

@@ -0,10 +1,14 @@

import * as CoffeeScript from 'coffee-script';
import ParseContext from './util/ParseContext';
import isChainedComparison from './util/isChainedComparison';
import lineColumnMapper from './util/lineColumnMapper';
import isInterpolatedString from './util/isInterpolatedString';
import locationsEqual from './util/locationsEqual';
import parseLiteral from './util/parseLiteral';
import trimNonMatchingParentheses from './util/trimNonMatchingParentheses';
import type from './util/type';
import { nodes as csParse } from 'coffee-script';
import { patchCoffeeScript } from './ext/coffee-script';
/**
* @param {string} source
* @param {{coffeeScriptParser: function(string): Object}} options
* @param {{coffeeScript: {nodes: function(string): Object, tokens: function(string): Array}}} options
* @returns {Program}

@@ -24,4 +28,5 @@ */

const parseCoffeeScript = options.coffeeScriptParser || csParse;
return /** @type Program */ convert(parseCoffeeScript(source), source, lineColumnMapper(source));
const CS = options.coffeeScript || CoffeeScript;
patchCoffeeScript(CS);
return /** @type Program */ convert(ParseContext.fromSource(source, CS.tokens, CS.nodes));
}

@@ -84,645 +89,899 @@

/**
* @param {Object} node
* @param {string} source
* @param {function(number, number): number} mapper
* @param ancestors
* @param {ParseContext} context
* @returns {Node}
*/
function convert(node, source, mapper, ancestors=[]) {
if (ancestors.length === 0) {
return makeNode('Program', node.locationData, {
body: makeNode('Block', node.locationData, {
statements: convertChild(node.expressions)
})
});
}
function convert(context) {
const { source, lineMap: mapper } = context;
return convertNode(context.ast);
switch (type(node)) {
case 'Value':
let value = convertChild(node.base);
node.properties.forEach(prop => {
value = accessOpForProperty(value, prop, node.base.locationData);
if (value.type === 'MemberAccessOp' && value.expression.type === 'MemberAccessOp') {
if (value.expression.memberName === 'prototype' && value.expression.raw.slice(-2) === '::') {
// Un-expand shorthand prototype access.
value = {
type: 'ProtoMemberAccessOp',
line: value.line,
column: value.column,
range: value.range,
raw: value.raw,
expression: value.expression.expression,
memberName: value.memberName
};
/**
* @param {Object} node
* @param ancestors
* @returns {Node}
*/
function convertNode(node, ancestors = []) {
if (ancestors.length === 0) {
let programNode = makeNode('Program', node.locationData, {
body: makeNode('Block', node.locationData, {
statements: convertChild(node.expressions)
})
});
Object.defineProperty(programNode, 'context', {
value: context,
enumerable: false
});
return programNode;
}
if (node.locationData) {
trimNonMatchingParentheses(source, node.locationData, mapper);
}
switch (type(node)) {
case 'Value':
let value = convertChild(node.base);
node.properties.forEach(prop => {
value = accessOpForProperty(value, prop, node.base.locationData);
if (value.type === 'MemberAccessOp' && value.expression.type === 'MemberAccessOp') {
if (value.expression.memberName === 'prototype' && value.expression.raw.slice(-2) === '::') {
// Un-expand shorthand prototype access.
value = {
type: 'ProtoMemberAccessOp',
line: value.line,
column: value.column,
range: value.range,
raw: value.raw,
expression: value.expression.expression,
memberName: value.memberName
};
}
}
});
return value;
case 'Literal':
if (node.value === 'this') {
return makeNode('This', node.locationData);
} else {
let start = mapper(node.locationData.first_line, node.locationData.first_column);
let end = mapper(node.locationData.last_line, node.locationData.last_column) + 1;
let raw = source.slice(start, end);
let literal = parseLiteral(raw, start);
if (!literal) {
if (raw[0] === '`' && raw[raw.length - 1] === '`') {
return makeNode('JavaScript', node.locationData, { data: node.value });
}
return makeNode('Identifier', node.locationData, { data: node.value });
} else if (literal.type === 'error') {
if (literal.error.type === 'unbalanced-quotes') {
// This is probably part of an interpolated string.
literal = parseLiteral(node.value);
return makeNode('String', node.locationData, { data: parseLiteral(node.value).data });
}
throw new Error(literal.error.message);
} else if (literal.type === 'string') {
return makeNode('String', node.locationData, { data: literal.data });
} else if (literal.type === 'int') {
return makeNode('Int', node.locationData, { data: literal.data });
} else if (literal.type === 'float') {
return makeNode('Float', node.locationData, { data: literal.data });
} else if (literal.type === 'Herestring') {
return makeNode('Herestring', node.locationData, { data: literal.data, padding: literal.padding });
} else {
throw new Error(`unknown literal type for value: ${JSON.stringify(literal)}`);
}
}
});
return value;
case 'Literal':
if (node.value === 'this') {
return makeNode('This', node.locationData);
} else {
const data = parseLiteral(node.value);
if (typeof data === 'string') {
return makeNode('String', node.locationData, {data});
} else if (typeof data === 'number') {
return makeNode(nodeTypeForLiteral(data), node.locationData, { data });
} else if (typeof data === 'undefined') {
return makeNode('Identifier', node.locationData, { data: node.value });
case 'Call':
if (node.isNew) {
return makeNode('NewOp', expandLocationLeftThrough(node.locationData, 'new'), {
ctor: convertChild(node.variable),
arguments: convertChild(node.args)
});
} else if (node.isSuper) {
if (node.args.length === 1 && type(node.args[0]) === 'Splat' && locationsEqual(node.args[0].locationData, node.locationData)) {
// Virtual splat argument.
return makeNode('FunctionApplication', node.locationData, {
function: makeNode('Super', node.locationData),
arguments: [{
type: 'Spread',
virtual: true,
expression: {
type: 'Identifier',
data: 'arguments',
virtual: true
}
}]
});
}
const superLocationData = {
first_line: node.locationData.first_line,
first_column: node.locationData.first_column,
last_line: node.locationData.first_line,
last_column: node.locationData.first_column + 'super'.length - 1
};
return makeNode('FunctionApplication', node.locationData, {
function: makeNode('Super', superLocationData),
arguments: convertChild(node.args)
});
} else {
throw new Error(`unknown literal type for value: ${data}`);
const result = makeNode(node.soak ? 'SoakedFunctionApplication' : 'FunctionApplication', node.locationData, {
function: convertChild(node.variable),
arguments: convertChild(node.args)
});
if (node.do) {
result.type = 'DoOp';
result.expression = result.function;
// The argument to `do` may not always be a function literal.
if (result.expression.parameters) {
result.expression.parameters = result.expression.parameters.map((param, i) => {
const arg = result.arguments[i];
if (arg.type === 'Identifier' && arg.data === param.data) {
return param;
}
return makeNode('DefaultParam', locationContainingNodes(node.args[i], node.variable.params[i]), {
param,
default: arg
});
});
}
delete result.function;
delete result.arguments;
}
return result;
}
}
case 'Call':
if (node.isNew) {
return makeNode('NewOp', expandLocationLeftThrough(node.locationData, 'new'), {
ctor: convertChild(node.variable),
arguments: convertChild(node.args)
});
} else if (node.isSuper) {
if (node.args.length === 1 && type(node.args[0]) === 'Splat' && node.args[0].locationData === node.locationData) {
// Virtual splat argument, ignore it.
node.args = [];
case 'Op':
const op = convertOperator(node);
if (op.type === 'PlusOp') {
if (isInterpolatedString(node, context)) {
op.type = 'ConcatOp';
let parentOp = ancestors.reduce((memo, ancestor) => type(ancestor) === 'Op' ? ancestor : memo, null);
if (!parentOp || !isInterpolatedString(parentOp, context)) {
return createTemplateLiteral(op);
}
}
}
return makeNode('SuperFunctionApplication', node.locationData, {
arguments: convertChild(node.args)
if (isChainedComparison(node) && !isChainedComparison(ancestors[ancestors.length - 1])) {
return makeNode('ChainedComparisonOp', node.locationData, {
expression: op
});
}
return op;
case 'Assign':
if (node.context === 'object') {
return makeNode('ObjectInitialiserMember', node.locationData, {
key: convertChild(node.variable),
expression: convertChild(node.value)
});
} else if (node.context && node.context.slice(-1) === '=') {
return makeNode('CompoundAssignOp', node.locationData, {
assignee: convertChild(node.variable),
expression: convertChild(node.value),
op: binaryOperatorNodeType(node.context.slice(0, -1))
})
} else {
return makeNode('AssignOp', node.locationData, {
assignee: convertChild(node.variable),
expression: convertChild(node.value)
});
}
case 'Obj':
return makeNode('ObjectInitialiser', node.locationData, {
members: node.properties.map(property => {
if (type(property) === 'Value') {
// shorthand property
const keyValue = convertChild(property);
return makeNode('ObjectInitialiserMember', property.locationData, {
key: keyValue,
expression: keyValue
});
}
return convertChild(property);
}).filter(node => node)
});
} else {
return makeNode(node.soak ? 'SoakedFunctionApplication' : 'FunctionApplication', node.locationData, {
function: convertChild(node.variable),
arguments: convertChild(node.args)
case 'Arr':
return makeNode('ArrayInitialiser', node.locationData, {
members: convertChild(node.objects)
});
}
case 'Op':
const op = convertOperator(node);
if (isChainedComparison(node) && !isChainedComparison(ancestors[ancestors.length - 1])) {
return makeNode('ChainedComparisonOp', node.locationData, {
expression: op
case 'Parens':
if (type(node.body) === 'Block') {
const expressions = node.body.expressions;
if (expressions.length === 1) {
return convertChild(expressions[0]);
} else {
const lastExpression = expressions[expressions.length - 1];
let result = convertChild(lastExpression);
for (let i = expressions.length - 2; i >= 0; i--) {
let left = expressions[i];
result = makeNode('SeqOp', locationContainingNodes(left, lastExpression), {
left: convertChild(left),
right: result
});
}
return result;
}
} else {
return convertChild(node.body);
}
case 'If':
let conditional = makeNode('Conditional', node.locationData, {
isUnless: Boolean(node.condition.inverted),
condition: convertChild(node.condition),
consequent: convertChild(node.body),
alternate: convertChild(node.elseBody)
});
}
return op;
if (conditional.condition.range[0] > conditional.consequent.range[0]) {
conditional.consequent.inline = true;
}
return conditional;
case 'Assign':
if (node.context === 'object') {
return makeNode('ObjectInitialiserMember', node.locationData, {
key: convertChild(node.variable),
expression: convertChild(node.value)
case 'Code':
const fnType = node.bound ? 'BoundFunction' :
node.isGenerator ? 'GeneratorFunction' : 'Function';
return makeNode(fnType, node.locationData, {
body: convertChild(node.body),
parameters: convertChild(node.params)
});
} else if (node.context && node.context.slice(-1) === '=') {
return makeNode('CompoundAssignOp', node.locationData, {
assignee: convertChild(node.variable),
expression: convertChild(node.value),
op: binaryOperatorNodeType(node.context.slice(0, -1))
})
} else {
return makeNode('AssignOp', node.locationData, {
assignee: convertChild(node.variable),
expression: convertChild(node.value)
});
}
case 'Obj':
return makeNode('ObjectInitialiser', node.locationData, {
members: node.properties.map(property => {
if (type(property) === 'Value') {
// shorthand property
const keyValue = convertChild(property);
return makeNode('ObjectInitialiserMember', property.locationData, {
key: keyValue,
expression: keyValue
});
case 'Param':
const param = convertChild(node.name);
if (node.value) {
return makeNode('DefaultParam', node.locationData, {
default: convertChild(node.value),
param
});
}
if (node.splat) {
return makeNode('Rest', node.locationData, {
expression: param
});
}
return param;
case 'Block':
if (node.expressions.length === 0) {
return null;
} else {
const block = makeNode('Block', node.locationData, {
statements: convertChild(node.expressions)
});
block.inline = false;
for (let i = block.range[0] - 1; i >= 0; i--) {
const char = source[i];
if (char === '\n') {
break;
} else if (char !== ' ' && char !== '\t') {
block.inline = true;
break;
}
}
return block;
}
return convertChild(property);
}).filter(node => node)
});
case 'Bool':
return makeNode('Bool', node.locationData, {
data: JSON.parse(node.val)
});
case 'Arr':
return makeNode('ArrayInitialiser', node.locationData, {
members: convertChild(node.objects)
});
case 'Null':
return makeNode('Null', node.locationData);
case 'Parens':
if (type(node.body) === 'Block') {
const expressions = node.body.expressions;
if (expressions.length === 1) {
return convertChild(expressions[0]);
case 'Undefined':
return makeNode('Undefined', node.locationData);
case 'Return':
return makeNode('Return', node.locationData, {
expression: node.expression ? convertChild(node.expression) : null
});
case 'For':
if (locationsEqual(node.body.locationData, node.locationData)) {
node.body.locationData = locationContainingNodes(...node.body.expressions);
}
node.locationData = locationWithLastPosition(node.locationData, node.body.locationData);
if (node.object) {
return makeNode('ForOf', node.locationData, {
keyAssignee: convertChild(node.index),
valAssignee: convertChild(node.name),
body: convertChild(node.body),
target: convertChild(node.source),
filter: convertChild(node.guard),
isOwn: node.own
});
} else {
const lastExpression = expressions[expressions.length - 1];
let result = convertChild(lastExpression);
for (let i = expressions.length - 2; i >= 0; i--) {
let left = expressions[i];
result = makeNode('SeqOp', locationContainingNodes(left, lastExpression), {
left: convertChild(left),
right: result
});
}
return result;
return makeNode('ForIn', node.locationData, {
keyAssignee: convertChild(node.index),
valAssignee: convertChild(node.name),
body: convertChild(node.body),
target: convertChild(node.source),
filter: convertChild(node.guard),
step: convertChild(node.step)
});
}
} else {
return convertChild(node.body);
}
case 'If':
if (type(node.condition) === 'Op' && node.condition.operator === '!') {
if (node.locationData === node.condition.locationData) {
// Virtual node for `unless` condition.
node.condition.locationData = null;
case 'While':
const result = makeNode('While', locationContainingNodes(node, node.condition, node.body), {
condition: convertChild(node.condition),
guard: convertChild(node.guard),
body: convertChild(node.body),
isUntil: node.condition.inverted === true
});
if (result.raw.indexOf('loop') === 0) {
result.condition = {
type: 'Bool',
data: true,
virtual: true
};
}
}
return makeNode('Conditional', node.locationData, {
condition: convertChild(node.condition),
consequent: convertChild(node.body),
alternate: convertChild(node.elseBody)
});
return result;
case 'Code':
const fnType = node.bound ? 'BoundFunction' :
node.isGenerator ? 'GeneratorFunction' : 'Function';
return makeNode(fnType, node.locationData, {
body: convertChild(node.body),
parameters: convertChild(node.params)
});
case 'Existence':
return makeNode('UnaryExistsOp', node.locationData, {
expression: convertChild(node.expression)
});
case 'Param':
const param = convertChild(node.name);
if (node.value) {
return makeNode('DefaultParam', node.locationData, {
default: convertChild(node.value),
param
case 'Class':
const nameNode = node.variable ? convertChild(node.variable) : null;
let ctor = null;
let boundMembers = [];
const body = (!node.body || node.body.expressions.length === 0) ? null : makeNode('Block', node.body.locationData, {
statements: node.body.expressions.reduce((statements, expr) => {
if (type(expr) === 'Value' && type(expr.base) === 'Obj') {
expr.base.properties.forEach(property => {
let key;
let value;
switch (type(property)) {
case 'Value':
// shorthand property
key = value = convertChild(property);
break;
case 'Comment':
return;
default:
key = convertChild(property.variable);
value = convertChild(property.value);
break;
}
if (key.data === 'constructor') {
statements.push(ctor = makeNode('Constructor', property.locationData, {
expression: value
}));
} else if (key.type === 'MemberAccessOp' && key.expression.type === 'This') {
statements.push(makeNode('AssignOp', property.locationData, {
assignee: key,
expression: value
}));
} else {
statements.push(makeNode('ClassProtoAssignOp', property.locationData, {
assignee: key,
expression: value
}));
}
if (value.type === 'BoundFunction') {
boundMembers.push(statements[statements.length - 1]);
}
});
} else {
statements.push(convertChild(expr));
}
return statements;
}, [])
});
}
if (node.splat) {
return makeNode('Rest', node.locationData, {
expression: param
return makeNode('Class', node.locationData, {
name: nameNode,
nameAssignee: nameNode,
body,
boundMembers,
parent: node.parent ? convertChild(node.parent) : null,
ctor
});
}
return param;
case 'Block':
if (node.expressions.length === 0) {
return null;
} else {
return makeNode('Block', node.locationData, {
statements: convertChild(node.expressions)
case 'Switch':
return makeNode('Switch', node.locationData, {
expression: convertChild(node.subject),
cases: node.cases.map(([conditions, body]) => {
if (!Array.isArray(conditions)) {
conditions = [conditions];
}
const loc = expandLocationLeftThrough(
locationContainingNodes(conditions[0], body),
'when '
);
return makeNode('SwitchCase', loc, {
conditions: convertChild(conditions),
consequent: convertChild(body)
})
}).filter(node => node),
alternate: convertChild(node.otherwise)
});
}
case 'Bool':
return makeNode('Bool', node.locationData, {
data: JSON.parse(node.val)
});
case 'Splat':
return makeNode('Spread', node.locationData, {
expression: convertChild(node.name)
});
case 'Null':
return makeNode('Null', node.locationData);
case 'Throw':
return makeNode('Throw', node.locationData, {
expression: convertChild(node.expression)
});
case 'Undefined':
return makeNode('Undefined', node.locationData);
case 'Try':
return makeNode('Try', node.locationData, {
body: convertChild(node.attempt),
catchAssignee: convertChild(node.errorVariable),
catchBody: convertChild(node.recovery),
finallyBody: convertChild(node.ensure)
});
case 'Return':
return makeNode('Return', node.locationData, {
expression: node.expression ? convertChild(node.expression) : null
});
case 'Range':
return makeNode('Range', node.locationData, {
left: convertChild(node.from),
right: convertChild(node.to),
isInclusive: !node.exclusive
});
case 'For':
if (node.body.locationData === node.locationData) {
node.body.locationData = locationContainingNodes(...node.body.expressions);
}
node.locationData = locationWithLastPosition(node.locationData, node.body.locationData);
if (node.object) {
return makeNode('ForOf', node.locationData, {
keyAssignee: convertChild(node.index),
valAssignee: convertChild(node.name),
body: convertChild(node.body),
target: convertChild(node.source),
filter: convertChild(node.guard),
isOwn: node.own
case 'In':
return makeNode('InOp', node.locationData, {
left: convertChild(node.object),
right: convertChild(node.array),
isNot: node.negated === true
});
} else {
return makeNode('ForIn', node.locationData, {
keyAssignee: convertChild(node.index),
valAssignee: convertChild(node.name),
body: convertChild(node.body),
target: convertChild(node.source),
filter: convertChild(node.guard),
step: convertChild(node.step)
});
}
case 'While':
return makeNode('While', locationContainingNodes(node, node.condition, node.body), {
condition: convertChild(node.condition),
body: convertChild(node.body)
});
case 'Expansion':
return makeNode('Expansion', node.locationData);
case 'Existence':
return makeNode('UnaryExistsOp', node.locationData, {
expression: convertChild(node.expression)
});
case 'Comment':
return null;
case 'Class':
const nameNode = node.variable ? convertChild(node.variable) : null;
case 'Extends':
return makeNode('ExtendsOp', node.locationData, {
left: convertChild(node.child),
right: convertChild(node.parent)
});
let ctor = null;
let boundMembers = [];
const body = (!node.body || node.body.expressions.length === 0) ? null : makeNode('Block', node.body.locationData, {
statements: node.body.expressions.reduce((statements, expr) => {
if (type(expr) === 'Value' && type(expr.base) === 'Obj') {
expr.base.properties.forEach(property => {
let key;
let value;
switch (type(property)) {
case 'Value':
// shorthand property
key = value = convertChild(property);
break;
default:
throw new Error(`unknown node type: ${type(node)}\n${JSON.stringify(node, null, 2)}`);
break;
}
case 'Comment':
return;
function convertChild(child) {
if (!child) {
return null;
} else if (Array.isArray(child)) {
return child.map(convertChild).filter(node => node);
} else {
return convertNode(child, [...ancestors, node]);
}
}
default:
key = convertChild(property.variable);
value = convertChild(property.value);
break;
function makeNode(type, loc, attrs = {}) {
const result = {type};
if (loc) {
const start = mapper(loc.first_line, loc.first_column);
const end = mapper(loc.last_line, loc.last_column) + 1;
result.line = loc.first_line + 1;
result.column = loc.first_column + 1;
result.range = [start, end];
} else {
result.virtual = true;
}
for (let key in attrs) {
if (attrs.hasOwnProperty(key)) {
let value = attrs[key];
result[key] = value;
if (value && result.range) {
(Array.isArray(value) ? value : [value]).forEach(node => {
if (node.range) {
// Expand the range to contain all the children.
if (result.range[0] > node.range[0]) {
result.range[0] = node.range[0];
}
if (result.range[1] < node.range[1]) {
result.range[1] = node.range[1];
}
}
if (key.data === 'constructor') {
statements.push(ctor = makeNode('Constructor', property.locationData, {
expression: value
}));
} else if (key.type === 'MemberAccessOp' && key.expression.type === 'This') {
statements.push(makeNode('AssignOp', property.locationData, {
assignee: key,
expression: value
}));
} else {
statements.push(makeNode('ClassProtoAssignOp', property.locationData, {
assignee: key,
expression: value
}));
}
if (value.type === 'BoundFunction') {
boundMembers.push(statements[statements.length - 1]);
}
});
} else {
statements.push(convertChild(expr));
}
return statements;
}, [])
});
}
}
// Shrink to be within the size of the source.
if (result.range) {
if (result.range[0] < 0) {
result.range[0] = 0;
}
if (result.range[1] > source.length) {
result.range[1] = source.length;
}
result.raw = source.slice(result.range[0], result.range[1]);
}
return result;
}
return makeNode('Class', node.locationData, {
name: nameNode,
nameAssignee: nameNode,
body,
boundMembers,
parent: node.parent ? convertChild(node.parent) : null,
ctor
});
function createTemplateLiteral(op) {
let stringStartTokenIndex = context.indexOfTokenAtOffset(op.range[0]);
for (; stringStartTokenIndex >= 0; stringStartTokenIndex--) {
if (context.tokenAtIndex(stringStartTokenIndex).type === 'STRING_START') {
break;
}
}
let stringEndTokenIndex = context.indexOfEndTokenForStartTokenAtIndex(stringStartTokenIndex);
if (stringEndTokenIndex === null) {
throw new Error('cannot find interpolation end for node');
}
op.type = 'TemplateLiteral';
op.range = [
context.tokenAtIndex(stringStartTokenIndex).range[0],
context.tokenAtIndex(stringEndTokenIndex).range[1]
];
op.raw = source.slice(...op.range);
case 'Switch':
return makeNode('Switch', node.locationData, {
expression: convertChild(node.subject),
cases: node.cases.map(([conditions, body]) => {
if (!Array.isArray(conditions)) {
conditions = [conditions];
}
const loc = expandLocationLeftThrough(
locationContainingNodes(conditions[0], body),
'when '
);
return makeNode('SwitchCase', loc, {
conditions: convertChild(conditions),
consequent: convertChild(body)
})
}).filter(node => node),
alternate: convertChild(node.otherwise)
});
let elements = [];
case 'Splat':
return makeNode('Spread', node.locationData, {
expression: convertChild(node.name)
});
function addElements({ left, right }) {
if (left.type === 'ConcatOp') {
addElements(left);
} else {
elements.push(left);
}
elements.push(right);
}
addElements(op);
case 'Throw':
return makeNode('Throw', node.locationData, {
expression: convertChild(node.expression)
});
let quasis = [];
let expressions = [];
let quote = op.raw.slice(0, 3) === '"""' ? '"""' : '"';
case 'Try':
return makeNode('Try', node.locationData, {
body: convertChild(node.attempt),
catchAssignee: convertChild(node.errorVariable),
catchBody: convertChild(node.recovery),
finallyBody: convertChild(node.ensure)
});
function buildFirstQuasi() {
// Find the start of the first interpolation, i.e. "#{a}".
// ^
let startOfInterpolation = op.range[0];
while (source[startOfInterpolation] !== '#') {
startOfInterpolation += 1;
}
let range = [op.range[0], startOfInterpolation];
return buildQuasi(range);
}
case 'Range':
return makeNode('Range', node.locationData, {
left: convertChild(node.from),
right: convertChild(node.to),
isInclusive: !node.exclusive
});
function buildLastQuasi() {
// Find the close of the last interpolation, i.e. "a#{b}".
// ^
let endOfInterpolation = op.range[1];
while (source[endOfInterpolation] !== '}') {
endOfInterpolation -= 1;
}
return buildQuasi([endOfInterpolation + 1, op.range[1]]);
}
case 'In':
return makeNode('InOp', node.locationData, {
left: convertChild(node.object),
right: convertChild(node.array)
});
function buildQuasi(range) {
let loc = mapper.invert(range[0]);
return {
type: 'String',
data: '',
raw: source.slice(...range),
line: loc.line + 1,
column: loc.column + 1,
range
};
}
case 'Expansion':
return makeNode('Expansion', node.locationData);
function quotesMatch(string) {
let leftTripleQuoted = string.slice(0, 3) === '"""';
let rightTripleQuoted = string.slice(-3) === '"""';
case 'Comment':
return null;
if (string.slice(-4) === '\\"""') {
// Don't count escaped quotes.
rightTripleQuoted = false;
}
case 'Extends':
return makeNode('ExtendsOp', node.locationData, {
left: convertChild(node.child),
right: convertChild(node.parent)
});
if (leftTripleQuoted !== rightTripleQuoted) {
// Unbalanced.
return false;
} else if (leftTripleQuoted && rightTripleQuoted) {
// We're set as long as we didn't double count.
return string.length >= 6;
}
default:
throw new Error(`unknown node type: ${type(node)}\n${JSON.stringify(node, null, 2)}`);
break;
}
let leftSingleQuoted = string.slice(0, 1) === '"';
let rightSingleQuoted = string.slice(-1) === '"';
function convertChild(child) {
if (!child) {
return null;
} else if (Array.isArray(child)) {
return child.map(convertChild).filter(node => node);
} else {
return convert(child, source, mapper, [...ancestors, node]);
}
}
if (string.slice(-2) === '\\"') {
// Don't count escaped quotes.
rightSingleQuoted = false;
}
function makeNode(type, loc, attrs={}) {
const result = { type };
if (loc) {
const start = mapper(loc.first_line, loc.first_column);
const end = mapper(loc.last_line, loc.last_column) + 1;
result.line = loc.first_line + 1;
result.column = loc.first_column + 1;
result.range = [start, end];
} else {
result.virtual = true;
}
for (let key in attrs) {
if (attrs.hasOwnProperty(key)) {
let value = attrs[key];
result[key] = value;
if (value && result.range) {
(Array.isArray(value) ? value : [value]).forEach(node => {
if (node.range) {
// Expand the range to contain all the children.
if (result.range[0] > node.range[0]) {
result.range[0] = node.range[0];
if (leftSingleQuoted !== rightSingleQuoted) {
// Unbalanced.
return false;
} else if (leftSingleQuoted && rightSingleQuoted) {
// We're set as long as we didn't double count.
return string.length >= 2;
}
}
elements.forEach((element, i) => {
if (i === 0) {
if (element.type === 'String') {
if (element.range[0] === op.range[0]) {
// This string is not interpolated, it's part of the string interpolation.
if (element.data === '' && element.raw.length > quote.length) {
// CoffeeScript includes the `#` in the raw value of a leading
// empty quasi string, but it shouldn't be there.
element = buildFirstQuasi();
}
if (result.range[1] < node.range[1]) {
result.range[1] = node.range[1];
}
quasis.push(element);
return;
}
});
}
}
if (element.type === 'String' && !quotesMatch(element.raw)) {
quasis.push(element);
} else {
if (quasis.length === 0) {
// This element is interpolated and is first, i.e. "#{a}".
quasis.push(buildFirstQuasi());
} else if (quasis.length < expressions.length + 1) {
let borderIndex = source.lastIndexOf('}#{', element.range[0]);
quasis.push(buildQuasi([borderIndex + 1, borderIndex + 1]));
}
expressions.push(element);
}
});
if (quasis.length < expressions.length + 1) {
quasis.push(buildLastQuasi());
}
op.quasis = quasis;
op.expressions = expressions;
delete op.left;
delete op.right;
return op;
}
// Shrink to be within the size of the source.
if (result.range) {
if (result.range[0] < 0) {
result.range[0] = 0;
}
if (result.range[1] > source.length) {
result.range[1] = source.length;
}
result.raw = source.slice(result.range[0], result.range[1]);
}
return result;
}
/**
* @param expression converted base
* @param prop CS node to convert
* @param loc CS location data for original base
*/
function accessOpForProperty(expression, prop, loc) {
switch (type(prop)) {
case 'Access':
return makeNode(prop.soak ? 'SoakedMemberAccessOp' : 'MemberAccessOp', mergeLocations(loc, prop.locationData), {
expression,
memberName: prop.name.value
});
/**
* @param expression converted base
* @param prop CS node to convertNode
* @param loc CS location data for original base
*/
function accessOpForProperty(expression, prop, loc) {
switch (type(prop)) {
case 'Access':
return makeNode(prop.soak ? 'SoakedMemberAccessOp' : 'MemberAccessOp', mergeLocations(loc, prop.locationData), {
expression,
memberName: prop.name.value
});
case 'Index':
return makeNode(prop.soak ? 'SoakedDynamicMemberAccessOp' : 'DynamicMemberAccessOp', expandLocationRightThrough(mergeLocations(loc, prop.locationData), ']'), {
expression,
indexingExpr: convert(prop.index, source, mapper, [...ancestors, node, prop])
});
case 'Index':
return makeNode(prop.soak ? 'SoakedDynamicMemberAccessOp' : 'DynamicMemberAccessOp', expandLocationRightThrough(mergeLocations(loc, prop.locationData), ']'), {
expression,
indexingExpr: convertNode(prop.index, [...ancestors, node, prop])
});
case 'Slice':
return makeNode('Slice', expandLocationRightThrough(mergeLocations(loc, prop.locationData), ']'), {
expression,
left: convertChild(prop.range.from),
right: convertChild(prop.range.to),
isInclusive: !prop.range.exclusive
});
case 'Slice':
return makeNode('Slice', expandLocationRightThrough(mergeLocations(loc, prop.locationData), ']'), {
expression,
left: convertChild(prop.range.from),
right: convertChild(prop.range.to),
isInclusive: !prop.range.exclusive
});
default:
throw new Error(`unknown property type: ${type(prop)}\n${JSON.stringify(prop, null, 2)}`)
default:
throw new Error(`unknown property type: ${type(prop)}\n${JSON.stringify(prop, null, 2)}`)
}
}
}
function binaryOperatorNodeType(operator) {
switch (operator) {
case '===':
return 'EQOp';
case '!==':
return 'NEQOp';
function binaryOperatorNodeType(operator) {
switch (operator) {
case '===':
return 'EQOp';
case '&&':
return 'LogicalAndOp';
case '!==':
return 'NEQOp';
case '||':
return 'LogicalOrOp';
case '&&':
return 'LogicalAndOp';
case '+':
return 'PlusOp';
case '||':
return 'LogicalOrOp';
case '-':
return 'SubtractOp';
case '+':
return 'PlusOp';
case '*':
return 'MultiplyOp';
case '-':
return 'SubtractOp';
case '/':
return 'DivideOp';
case '*':
return 'MultiplyOp';
case '%':
return 'RemOp';
case '/':
return 'DivideOp';
case '&':
return 'BitAndOp';
case '%':
return 'RemOp';
case '|':
return 'BitOrOp';
case '%%':
return 'ModuloOp';
case '^':
return 'BitXorOp';
case '&':
return 'BitAndOp';
case '<':
return 'LTOp';
case '|':
return 'BitOrOp';
case '>':
return 'GTOp';
case '^':
return 'BitXorOp';
case '<=':
return 'LTEOp';
case '<':
return 'LTOp';
case '>=':
return 'GTEOp';
case '>':
return 'GTOp';
case 'in':
return 'OfOp';
case '<=':
return 'LTEOp';
case '?':
return 'ExistsOp';
case '>=':
return 'GTEOp';
case 'instanceof':
return 'InstanceofOp';
case 'in':
return 'OfOp';
case '<<':
return 'LeftShiftOp';
case '?':
return 'ExistsOp';
case '>>':
return 'SignedRightShiftOp';
case 'instanceof':
return 'InstanceofOp';
case '>>>':
return 'UnsignedRightShiftOp';
case '<<':
return 'LeftShiftOp';
case '**':
return 'ExpOp';
case '>>':
return 'SignedRightShiftOp';
case '//':
return 'FloorDivideOp';
case '>>>':
return 'UnsignedRightShiftOp';
default:
return null;
case '**':
return 'ExpOp';
case '//':
return 'FloorDivideOp';
default:
return null;
}
}
}
function convertOperator(op) {
let nodeType;
function convertOperator(op) {
let nodeType;
if (op.second) {
nodeType = binaryOperatorNodeType(op.operator);
if (op.second) {
nodeType = binaryOperatorNodeType(op.operator);
if (!nodeType) {
throw new Error(`unknown binary operator: ${op.operator}`);
}
if (!nodeType) {
throw new Error(`unknown binary operator: ${op.operator}`);
}
return makeNode(nodeType, op.locationData, {
left: convert(op.first, source, mapper, [...ancestors, op]),
right: convert(op.second, source, mapper, [...ancestors, op])
});
} else {
switch (op.operator) {
case '+':
nodeType = 'UnaryPlusOp';
break;
let result = makeNode(nodeType, op.locationData, {
left: convertNode(op.first, [...ancestors, op]),
right: convertNode(op.second, [...ancestors, op])
});
if (result.type === 'InstanceofOp' || result.type === 'OfOp') {
result.isNot = op.inverted === true;
}
return result;
} else {
switch (op.operator) {
case '+':
nodeType = 'UnaryPlusOp';
break;
case '-':
nodeType = 'UnaryNegateOp';
break;
case '-':
nodeType = 'UnaryNegateOp';
break;
case 'typeof':
nodeType = 'TypeofOp';
break;
case 'typeof':
nodeType = 'TypeofOp';
break;
case '!':
nodeType = 'LogicalNotOp';
break;
case '!':
nodeType = 'LogicalNotOp';
break;
case '~':
nodeType = 'BitNotOp';
break;
case '~':
nodeType = 'BitNotOp';
break;
case '--':
nodeType = op.flip ? 'PostDecrementOp' : 'PreDecrementOp';
break;
case '--':
nodeType = op.flip ? 'PostDecrementOp' : 'PreDecrementOp';
break;
case '++':
nodeType = op.flip ? 'PostIncrementOp' : 'PreIncrementOp';
break;
case '++':
nodeType = op.flip ? 'PostIncrementOp' : 'PreIncrementOp';
break;
case 'delete':
nodeType = 'DeleteOp';
break;
case 'delete':
nodeType = 'DeleteOp';
break;
case 'new':
// Parentheses-less "new".
return makeNode('NewOp', op.locationData, {
ctor: convertChild(op.first),
arguments: []
});
case 'new':
// Parentheses-less "new".
return makeNode('NewOp', op.locationData, {
ctor: convertChild(op.first),
arguments: []
});
case 'yield':
return makeNode('Yield', op.locationData, {
expression: convertChild(op.first)
});
case 'yield':
return makeNode('Yield', op.locationData, {
expression: convertChild(op.first)
});
default:
throw new Error(`unknown unary operator: ${op.operator}`);
default:
throw new Error(`unknown unary operator: ${op.operator}`);
}
return makeNode(nodeType, op.locationData, {
expression: convertNode(op.first, [...ancestors, op])
});
}
return makeNode(nodeType, op.locationData, {
expression: convert(op.first, source, mapper, [...ancestors, op])
});
}
}
function expandLocationRightThrough(loc, string) {
let offset = mapper(loc.last_line, loc.last_column) + 1;
offset = source.indexOf(string, offset);
function expandLocationRightThrough(loc, string) {
let offset = mapper(loc.last_line, loc.last_column) + 1;
offset = source.indexOf(string, offset);
if (offset < 0) {
throw new Error(
`unable to expand location ending at ${loc.last_line + 1}:${loc.last_column + 1} ` +
`because it is not followed by ${JSON.stringify(string)}`
);
if (offset < 0) {
throw new Error(
`unable to expand location ending at ${loc.last_line + 1}:${loc.last_column + 1} ` +
`because it is not followed by ${JSON.stringify(string)}`
);
}
const newLoc = mapper.invert(offset + string.length - 1);
return {
first_line: loc.first_line,
first_column: loc.first_column,
last_line: newLoc.line,
last_column: newLoc.column
};
}
const newLoc = mapper.invert(offset + string.length - 1);
function expandLocationLeftThrough(loc, string) {
let offset = mapper(loc.first_line, loc.first_column);
offset = source.lastIndexOf(string, offset);
return {
first_line: loc.first_line,
first_column: loc.first_column,
last_line: newLoc.line,
last_column: newLoc.column
};
}
if (offset < 0) {
throw new Error(
`unable to expand location starting at ${loc.first_line + 1}:${loc.first_column + 1} ` +
`because it is not preceded by ${JSON.stringify(string)}`
);
}
function expandLocationLeftThrough(loc, string) {
let offset = mapper(loc.first_line, loc.first_column);
offset = source.lastIndexOf(string, offset);
const newLoc = mapper.invert(offset);
if (offset < 0) {
throw new Error(
`unable to expand location starting at ${loc.first_line + 1}:${loc.first_column + 1} ` +
`because it is not preceded by ${JSON.stringify(string)}`
);
return {
first_line: newLoc.line,
first_column: newLoc.column,
last_line: loc.last_line,
last_column: loc.last_column
};
}
const newLoc = mapper.invert(offset);
return {
first_line: newLoc.line,
first_column: newLoc.column,
last_line: loc.last_line,
last_column: loc.last_column
};
}

@@ -729,0 +988,0 @@ }

/**
* @param {string} string
* @returns {string|number}
* @param {number=} offset
* @returns {*}
*/
export default function parseLiteral(string) {
if (string.slice(0, 3) === '"""') {
return parseQuotedString(string, '"""');
} else if (string.slice(0, 3) === "'''") {
return parseQuotedString(string, "'''");
} else if (string[0] === "'") {
export default function parseLiteral(string, offset=0) {
if (string.slice(0, 3) === '"""' || string.slice(-3) === '"""') {
return parseHerestring(string, '"""', offset);
} else if (string.slice(0, 3) === "'''" || string.slice(-3) === "'''") {
return parseHerestring(string, "'''", offset);
} else if (string[0] === "'" || string[string.length - 1] === "'") {
return parseQuotedString(string, "'");
} else if (string[0] === '"') {
} else if (string[0] === '"' || string[string.length - 1] === '"') {
return parseQuotedString(string, '"');
} else if (/^\d+$/.test(string)) {
return parseInt(string);
return parseInteger(string);
} else if (/^\d*\.\d+$/.test(string)) {
return parseFloat(string);
return parseFloatingPoint(string);
} else if (/^0x[\da-f]+$/i.test(string)) {
return parseHexidecimal(string);
} else if (/^0o[0-7]+$/i.test(string)) {
return parseOctal(string);
}
}
function parseHerestring(string, quote, offset=0) {
let { error, data } = parseQuotedString(string, quote);
if (error) {
return { type: 'error', error };
}
let { leadingMargin, trailingMargin, ranges } = getIndentInfo(string, 3, string.length - 3);
let indentSize = sharedIndentSize(ranges);
let padding = [];
let contentStart = offset + 3;
let contentEnd = offset + string.length - 3;
if (leadingMargin) {
padding.push([contentStart, contentStart + leadingMargin]);
}
if (indentSize) {
ranges.forEach(([start, end]) => {
if (end - start >= indentSize) {
padding.push([offset + start, offset + start + indentSize]);
}
});
}
if (trailingMargin) {
padding.push([contentEnd - trailingMargin, contentEnd]);
}
for (let i = padding.length - 1; i >= 0; i--) {
let [ start, end ] = padding[i];
data = data.slice(0, start - contentStart) + data.slice(end - contentStart);
}
return { type: 'Herestring', data, padding };
}
/**
* @param {string} string
* @param {string} quote
* @returns {string}
* @returns {*}
*/
function parseQuotedString(string, quote) {
if (string.slice(0, quote.length) !== quote || string.slice(-quote.length) !== quote) {
throw new Error(`tried to parse quoted string not wrapped in quotes: ${string}`);
return {
type: 'error',
error: {
type: 'unbalanced-quotes',
message: `tried to parse quoted string not wrapped in quotes: ${string}`
}
};
}

@@ -43,3 +88,9 @@

} else {
throw new Error(`found ${chr} when looking for a hex character`);
return {
type: 'error',
error: {
type: 'invalid-hex-character',
message: `found ${chr} when looking for a hex character`
}
};
}

@@ -84,7 +135,15 @@ }

case 'x':
result += String.fromCharCode(hex(2));
let x = hex(2);
if (x.type === 'error') {
return x;
}
result += String.fromCharCode(x);
break;
case 'u':
result += String.fromCharCode(hex(4));
let u = hex(4);
if (u.type === 'error') {
return u;
}
result += String.fromCharCode(u);
break;

@@ -104,3 +163,9 @@

if (string.slice(p - 1, p - 1 + quote.length) === quote) {
throw new Error('unexpected closing quote before the end of the string');
return {
type: 'error',
error: {
type: 'unexpected-closing-quote',
message: 'unexpected closing quote before the end of the string'
}
};
} else {

@@ -117,3 +182,3 @@ result += chr;

return result;
return { type: 'string', data: result };
}

@@ -123,6 +188,92 @@

* @param {string} string
* @returns {number}
* @returns {{type: string, data: number}}
*/
function parseInteger(string) {
return { type: 'int', data: parseInt(string, 10) };
}
/**
* @param {string} string
* @returns {{type: string, data: number}}
*/
function parseFloatingPoint(string) {
return { type: 'float', data: parseFloat(string) };
}
/**
* @param {string} string
* @returns {{type: string, data: number}}
*/
function parseHexidecimal(string) {
return parseInt(string.slice(2), 16);
return { type: 'int', data: parseInt(string.slice(2), 16) };
}
/**
* @param {string} string
* @returns {{type: string, data: number}}
*/
function parseOctal(string) {
return { type: 'int', data: parseInt(string.slice(2), 8) };
}
/**
* @param {string} source
* @param {number=} start
* @param {number=} end
* @returns {{leadingMargin: number, trailingMargin: number, ranges: Array<Array<number>>}}
*/
function getIndentInfo(source, start=0, end=source.length) {
const ranges = [];
let leadingMargin = 0;
while (source[start + leadingMargin] === ' ') {
leadingMargin += ' '.length;
}
if (source[start + leadingMargin] === '\n') {
leadingMargin += '\n'.length;
start += leadingMargin;
}
let trailingMargin = 0;
while (source[end - trailingMargin - ' '.length] === ' ') {
trailingMargin += ' '.length;
}
if (source[end - trailingMargin - '\n'.length] === '\n') {
trailingMargin += '\n'.length;
end -= trailingMargin;
}
for (let index = start; index < end; index++) {
if (index === start || source[index - 1] === '\n') {
if (source[index] !== '\n') {
let start = index;
while (source[index] === ' ') {
index++;
}
ranges.push([start, index]);
}
}
}
return {
leadingMargin,
trailingMargin,
ranges
};
}
/**
* @param {Array<Array<number>>} ranges
* @returns {number}
*/
function sharedIndentSize(ranges) {
let size = null;
ranges.forEach(([start, end]) => {
if (size === null || (start !== end && end - start < size)) {
size = end - start;
}
});
return size === null ? 0 : size;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc