decaffeinate-parser
Advanced tools
Comparing version 1.3.1 to 2.0.0
import * as CoffeeScript from 'coffee-script'; | ||
import binarySearch from 'binary-search'; | ||
import lex, { RELATION, OPERATOR, HERECOMMENT, COMMENT, NEWLINE, IF, RBRACKET, LBRACKET } from 'coffee-lex'; | ||
@@ -14,3 +13,3 @@ import { inspect } from 'util'; | ||
while ((offset = source.indexOf('\n', offset)) > 0) { | ||
while ((offset = source.indexOf('\n', offset)) >= 0) { | ||
offset += '\n'.length; | ||
@@ -34,11 +33,2 @@ offsets.push(offset); | ||
/** | ||
* @param {Object} first | ||
* @param {Object} second | ||
* @returns {boolean} | ||
*/ | ||
function locationsEqual(first, second) { | ||
return first.first_line === second.first_line && first.first_column === second.first_column && first.last_line === second.last_line && first.last_column === second.last_column; | ||
} | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { | ||
@@ -164,3 +154,2 @@ return typeof obj; | ||
* @param {string} source | ||
* @param {Array} tokens | ||
* @param {SourceTokenList} sourceTokens | ||
@@ -170,3 +159,3 @@ * @param {Object} ast | ||
function ParseContext(source, tokens, sourceTokens, ast) { | ||
function ParseContext(source, sourceTokens, ast) { | ||
classCallCheck(this, ParseContext); | ||
@@ -177,6 +166,2 @@ | ||
this.ast = ast; | ||
/** | ||
* @deprecated | ||
*/ | ||
this.tokens = this.transformTokens(tokens); | ||
this.sourceTokens = sourceTokens; | ||
@@ -204,161 +189,3 @@ } | ||
/** | ||
* @param {Object} node | ||
* @returns {Array} | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'tokensForNode', | ||
value: function tokensForNode(node) { | ||
var _getRange = this.getRange(node); | ||
var _getRange2 = slicedToArray(_getRange, 2); | ||
var start = _getRange2[0]; | ||
var end = _getRange2[1]; | ||
var firstTokenIndex = this.indexOfTokenAtOffset(start); | ||
if (firstTokenIndex < 0) { | ||
return []; | ||
} | ||
var result = []; | ||
for (var i = firstTokenIndex; i < this.tokens.length; i++) { | ||
var token = this.tokens[i]; | ||
if (token.range[1] > end) { | ||
break; | ||
} | ||
result.push(token); | ||
} | ||
return result; | ||
} | ||
/** | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'tokenAtIndex', | ||
value: function tokenAtIndex(index) { | ||
return this.tokens[index] || null; | ||
} | ||
/** | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'indexOfTokenAtOffset', | ||
value: function indexOfTokenAtOffset(offset) { | ||
var tokens = this.tokens; | ||
var index = binarySearch(tokens, offset, function (token, offset) { | ||
return token.range[0] - offset; | ||
}); | ||
while (tokens[index - 1] && tokens[index - 1].range[0] === offset) { | ||
// There can be multiple tokens that start at the same offset, e.g. | ||
// STRING and STRING_START with string interpolation. | ||
index--; | ||
} | ||
return index; | ||
} | ||
/** | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'indexOfTokenFromOffset', | ||
value: function indexOfTokenFromOffset(offset) { | ||
var index = this.indexOfTokenAtOffset(offset); | ||
if (index < 0) { | ||
for (var i = ~index; i < this.tokens.length; i++) { | ||
if (this.tokens[i].range[0] >= offset) { | ||
return i; | ||
} | ||
} | ||
} | ||
return index; | ||
} | ||
/** | ||
* @param {number} index | ||
* @returns {?number} | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'indexOfEndTokenForStartTokenAtIndex', | ||
value: function indexOfEndTokenForStartTokenAtIndex(index) { | ||
var startToken = this.tokenAtIndex(index); | ||
var expectedEndTokenType = void 0; | ||
switch (startToken.type) { | ||
case 'PARAM_START': | ||
expectedEndTokenType = 'PARAM_END'; | ||
break; | ||
case 'STRING_START': | ||
expectedEndTokenType = 'STRING_END'; | ||
break; | ||
default: | ||
throw new Error('unexpected start token type: ' + startToken.type); | ||
} | ||
var tokens = this.tokens; | ||
for (var i = index; i < tokens.length; i++) { | ||
if (tokens[i].type === expectedEndTokenType) { | ||
return i; | ||
} | ||
} | ||
return null; | ||
} | ||
/** | ||
* @param {Object} left | ||
* @param {Object} right | ||
* @returns {Array} | ||
* @deprecated | ||
*/ | ||
}, { | ||
key: 'tokensBetweenNodes', | ||
value: function tokensBetweenNodes(left, right) { | ||
var leftIndex = this.indexOfTokenFromOffset(this.getRange(left)[1]); | ||
var rightIndex = this.indexOfTokenAtOffset(this.getRange(right)[0]); | ||
if (leftIndex < 0 || rightIndex < 0) { | ||
return []; | ||
} | ||
return this.tokens.slice(leftIndex, rightIndex); | ||
} | ||
}, { | ||
key: 'transformTokens', | ||
value: function transformTokens(tokens) { | ||
var _this2 = this; | ||
return tokens.map(function (token) { | ||
return _this2.transformToken(token); | ||
}); | ||
} | ||
/** | ||
* @param token | ||
* @returns {{type: string, data: string, range: number[], original: *}} | ||
*/ | ||
}, { | ||
key: 'transformToken', | ||
value: function transformToken(token) { | ||
return { | ||
type: token[0], | ||
data: token[1], | ||
range: this.getRange(token[2]), | ||
original: token | ||
}; | ||
} | ||
/** | ||
* @param {string} source | ||
* @param {function(string, Object=): Array} lex | ||
* @param {function(string): SourceTokenList} sourceLex | ||
@@ -371,7 +198,6 @@ * @param {function(string|Array): Object} parse | ||
key: 'fromSource', | ||
value: function fromSource(source, lex, sourceLex, parse) { | ||
value: function fromSource(source, sourceLex, parse) { | ||
try { | ||
var tokens = lex(source, { rewrite: false }); | ||
var sourceTokens = sourceLex(source); | ||
return new ParseContext(source, tokens, sourceTokens, parse(source)); | ||
return new ParseContext(source, sourceTokens, parse(source)); | ||
} catch (ex) { | ||
@@ -480,2 +306,11 @@ if (ex instanceof SyntaxError) { | ||
/** | ||
* @param {Object} first | ||
* @param {Object} second | ||
* @returns {boolean} | ||
*/ | ||
function locationsEqual(first, second) { | ||
return first.first_line === second.first_line && first.first_column === second.first_column && first.last_line === second.last_line && first.last_column === second.last_column; | ||
} | ||
/** | ||
* @param {string} string | ||
@@ -865,3 +700,3 @@ * @param {number=} offset | ||
var context = ParseContext.fromSource(source, CS.tokens, lex, CS.nodes); | ||
var context = ParseContext.fromSource(source, lex, CS.nodes); | ||
@@ -1190,513 +1025,618 @@ if (source.length === 0) { | ||
switch (type(node)) { | ||
case 'Value': | ||
var value = convertChild(node.base); | ||
node.properties.forEach(function (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 | ||
}; | ||
var _ret = function () { | ||
switch (type(node)) { | ||
case 'Value': | ||
var value = convertChild(node.base); | ||
node.properties.forEach(function (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; | ||
}); | ||
return { | ||
v: value | ||
}; | ||
case 'Literal': | ||
if (node.value === 'this') { | ||
return makeNode('This', node.locationData); | ||
} else { | ||
var start = mapper(node.locationData.first_line, node.locationData.first_column); | ||
var end = mapper(node.locationData.last_line, node.locationData.last_column) + 1; | ||
var raw = source.slice(start, end); | ||
var literal = parseLiteral(raw, start); | ||
if (!literal) { | ||
if (raw[0] === '`' && raw[raw.length - 1] === '`') { | ||
return makeNode('JavaScript', node.locationData, { data: node.value }); | ||
} else if (raw.slice(0, '///'.length) === '///') { | ||
var _ret = function () { | ||
var flags = raw.match(HEREGEX_PATTERN)[2]; | ||
case 'Literal': | ||
if (node.value === 'this') { | ||
return { | ||
v: makeNode('This', node.locationData) | ||
}; | ||
} else { | ||
var start = mapper(node.locationData.first_line, node.locationData.first_column); | ||
var end = mapper(node.locationData.last_line, node.locationData.last_column) + 1; | ||
var raw = source.slice(start, end); | ||
var literal = parseLiteral(raw, start); | ||
if (!literal) { | ||
if (raw[0] === '`' && raw[raw.length - 1] === '`') { | ||
return { | ||
v: makeNode('RegExp', node.locationData, { | ||
data: node.value, | ||
flags: ['g', 'i', 'm', 'y'].reduce(function (memo, flag) { | ||
memo[flag] = flags.indexOf(flag) >= 0; | ||
return memo; | ||
}, {}) | ||
}) | ||
v: makeNode('JavaScript', node.locationData, { data: node.value }) | ||
}; | ||
}(); | ||
} else if (raw.slice(0, '///'.length) === '///') { | ||
var _ret2 = function () { | ||
var flags = raw.match(HEREGEX_PATTERN)[2]; | ||
return { | ||
v: { | ||
v: makeNode('RegExp', node.locationData, { | ||
data: node.value, | ||
flags: ['g', 'i', 'm', 'y'].reduce(function (memo, flag) { | ||
memo[flag] = flags.indexOf(flag) >= 0; | ||
return memo; | ||
}, {}) | ||
}) | ||
} | ||
}; | ||
}(); | ||
if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; | ||
if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; | ||
} | ||
return { | ||
v: 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 { | ||
v: makeNode('String', node.locationData, { data: parseLiteral(node.value).data }) | ||
}; | ||
} | ||
throw new Error(literal.error.message); | ||
} else if (literal.type === 'string') { | ||
return { | ||
v: makeNode('String', node.locationData, { data: literal.data }) | ||
}; | ||
} else if (literal.type === 'int') { | ||
return { | ||
v: makeNode('Int', node.locationData, { data: literal.data }) | ||
}; | ||
} else if (literal.type === 'float') { | ||
return { | ||
v: makeNode('Float', node.locationData, { data: literal.data }) | ||
}; | ||
} else if (literal.type === 'Herestring') { | ||
return { | ||
v: makeNode('Herestring', node.locationData, { data: literal.data, padding: literal.padding }) | ||
}; | ||
} else if (literal.type === 'RegExp') { | ||
return { | ||
v: makeNode('RegExp', node.locationData, { data: literal.data, flags: literal.flags }) | ||
}; | ||
} else { | ||
throw new Error('unknown literal type for value: ' + JSON.stringify(literal)); | ||
} | ||
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 }); | ||
} | ||
case 'Call': | ||
if (node.isNew) { | ||
return { | ||
v: 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 { | ||
v: makeNode('FunctionApplication', node.locationData, { | ||
function: makeNode('Super', node.locationData), | ||
arguments: [{ | ||
type: 'Spread', | ||
virtual: true, | ||
expression: { | ||
type: 'Identifier', | ||
data: 'arguments', | ||
virtual: true | ||
} | ||
}] | ||
}) | ||
}; | ||
} | ||
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 if (literal.type === 'RegExp') { | ||
return makeNode('RegExp', node.locationData, { data: literal.data, flags: literal.flags }); | ||
var 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 { | ||
v: makeNode('FunctionApplication', node.locationData, { | ||
function: makeNode('Super', superLocationData), | ||
arguments: convertChild(node.args) | ||
}) | ||
}; | ||
} else { | ||
throw new Error('unknown literal type for value: ' + JSON.stringify(literal)); | ||
} | ||
} | ||
var _ret3 = function () { | ||
var result = makeNode(node.soak ? 'SoakedFunctionApplication' : 'FunctionApplication', node.locationData, { | ||
function: convertChild(node.variable), | ||
arguments: convertChild(node.args) | ||
}); | ||
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 | ||
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(function (param, i) { | ||
var 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: param, | ||
default: arg | ||
}); | ||
}); | ||
} | ||
}] | ||
}); | ||
delete result.function; | ||
delete result.arguments; | ||
} | ||
return { | ||
v: { | ||
v: result | ||
} | ||
}; | ||
}(); | ||
if ((typeof _ret3 === 'undefined' ? 'undefined' : _typeof(_ret3)) === "object") return _ret3.v; | ||
} | ||
var 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 | ||
case 'Op': | ||
var op = convertOperator(node); | ||
if (isInterpolatedString(node, ancestors, context)) { | ||
return { | ||
v: createTemplateLiteral(op) | ||
}; | ||
} | ||
if (isChainedComparison(node) && !isChainedComparison(ancestors[ancestors.length - 1])) { | ||
return { | ||
v: makeNode('ChainedComparisonOp', node.locationData, { | ||
expression: op | ||
}) | ||
}; | ||
} | ||
return { | ||
v: op | ||
}; | ||
return makeNode('FunctionApplication', node.locationData, { | ||
function: makeNode('Super', superLocationData), | ||
arguments: convertChild(node.args) | ||
}); | ||
} else { | ||
var _ret2 = function () { | ||
var 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(function (param, i) { | ||
var arg = result.arguments[i]; | ||
case 'Assign': | ||
if (node.context === 'object') { | ||
return { | ||
v: makeNode('ObjectInitialiserMember', node.locationData, { | ||
key: convertChild(node.variable), | ||
expression: convertChild(node.value) | ||
}) | ||
}; | ||
} else if (node.context && node.context.slice(-1) === '=') { | ||
return { | ||
v: makeNode('CompoundAssignOp', node.locationData, { | ||
assignee: convertChild(node.variable), | ||
expression: convertChild(node.value), | ||
op: binaryOperatorNodeType(node.context.slice(0, -1)) | ||
}) | ||
}; | ||
} else { | ||
return { | ||
v: makeNode('AssignOp', node.locationData, { | ||
assignee: convertChild(node.variable), | ||
expression: convertChild(node.value) | ||
}) | ||
}; | ||
} | ||
if (arg.type === 'Identifier' && arg.data === param.data) { | ||
return param; | ||
} | ||
case 'Obj': | ||
return { | ||
v: makeNode('ObjectInitialiser', node.locationData, { | ||
members: node.properties.map(function (property) { | ||
if (type(property) === 'Value') { | ||
// shorthand property | ||
var keyValue = convertChild(property); | ||
return makeNode('ObjectInitialiserMember', property.locationData, { | ||
key: keyValue, | ||
expression: keyValue | ||
}); | ||
} | ||
return makeNode('DefaultParam', locationContainingNodes(node.args[i], node.variable.params[i]), { | ||
param: param, | ||
default: arg | ||
}); | ||
return convertChild(property); | ||
}).filter(function (node) { | ||
return node; | ||
}) | ||
}) | ||
}; | ||
case 'Arr': | ||
return { | ||
v: makeNode('ArrayInitialiser', node.locationData, { | ||
members: convertChild(node.objects) | ||
}) | ||
}; | ||
case 'Parens': | ||
if (type(node.body) === 'Block') { | ||
var expressions = node.body.expressions; | ||
if (expressions.length === 1) { | ||
return { | ||
v: convertChild(expressions[0]) | ||
}; | ||
} else { | ||
var lastExpression = expressions[expressions.length - 1]; | ||
var _result2 = convertChild(lastExpression); | ||
for (var i = expressions.length - 2; i >= 0; i--) { | ||
var left = expressions[i]; | ||
_result2 = makeNode('SeqOp', locationContainingNodes(left, lastExpression), { | ||
left: convertChild(left), | ||
right: _result2 | ||
}); | ||
} | ||
delete result.function; | ||
delete result.arguments; | ||
return { | ||
v: _result2 | ||
}; | ||
} | ||
} else { | ||
return { | ||
v: result | ||
v: convertChild(node.body) | ||
}; | ||
}(); | ||
} | ||
if ((typeof _ret2 === 'undefined' ? 'undefined' : _typeof(_ret2)) === "object") return _ret2.v; | ||
} | ||
case 'If': | ||
{ | ||
var condition = convertChild(node.condition); | ||
var consequent = convertChild(node.body); | ||
var alternate = convertChild(node.elseBody); | ||
var isUnless = false; | ||
case 'Op': | ||
var op = convertOperator(node); | ||
if (isInterpolatedString(node, ancestors, context)) { | ||
return createTemplateLiteral(op); | ||
} | ||
if (isChainedComparison(node) && !isChainedComparison(ancestors[ancestors.length - 1])) { | ||
return makeNode('ChainedComparisonOp', node.locationData, { | ||
expression: op | ||
}); | ||
} | ||
return op; | ||
if (consequent && consequent.range[0] < condition.range[0]) { | ||
// POST-if, so look for tokens between the consequent and the condition | ||
consequent.inline = true; | ||
var lastConsequentTokenIndex = context.sourceTokens.indexOfTokenEndingAtSourceIndex(consequent.range[1]); | ||
var firstConditionTokenIndex = context.sourceTokens.indexOfTokenStartingAtSourceIndex(condition.range[0]); | ||
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) | ||
}); | ||
} | ||
for (var _i = lastConsequentTokenIndex; _i !== firstConditionTokenIndex; _i = _i.next()) { | ||
var token = context.sourceTokens.tokenAtIndex(_i); | ||
if (token.type === IF) { | ||
isUnless = source.slice(token.start, token.end) === 'unless'; | ||
break; | ||
} | ||
} | ||
} else { | ||
// Regular `if`, so look at the start of the node. | ||
var _firstConditionTokenIndex = context.sourceTokens.indexOfTokenStartingAtSourceIndex(condition.range[0]); | ||
case 'Obj': | ||
return makeNode('ObjectInitialiser', node.locationData, { | ||
members: node.properties.map(function (property) { | ||
if (type(property) === 'Value') { | ||
// shorthand property | ||
var keyValue = convertChild(property); | ||
return makeNode('ObjectInitialiserMember', property.locationData, { | ||
key: keyValue, | ||
expression: keyValue | ||
}); | ||
for (var _i2 = _firstConditionTokenIndex; _i2 !== null; _i2 = _i2.previous()) { | ||
var _token = context.sourceTokens.tokenAtIndex(_i2); | ||
if (_token.type === IF) { | ||
isUnless = source.slice(_token.start, _token.end) === 'unless'; | ||
break; | ||
} | ||
} | ||
} | ||
return convertChild(property); | ||
}).filter(function (node) { | ||
return node; | ||
}) | ||
}); | ||
return { | ||
v: makeNode('Conditional', node.locationData, { | ||
isUnless: isUnless, | ||
condition: condition, | ||
consequent: consequent, | ||
alternate: alternate | ||
}) | ||
}; | ||
} | ||
case 'Arr': | ||
return makeNode('ArrayInitialiser', node.locationData, { | ||
members: convertChild(node.objects) | ||
}); | ||
case 'Code': | ||
var fnType = node.bound ? 'BoundFunction' : node.isGenerator ? 'GeneratorFunction' : 'Function'; | ||
return { | ||
v: makeNode(fnType, node.locationData, { | ||
body: convertChild(node.body), | ||
parameters: convertChild(node.params) | ||
}) | ||
}; | ||
case 'Parens': | ||
if (type(node.body) === 'Block') { | ||
var expressions = node.body.expressions; | ||
if (expressions.length === 1) { | ||
return convertChild(expressions[0]); | ||
} else { | ||
var lastExpression = expressions[expressions.length - 1]; | ||
var _result = convertChild(lastExpression); | ||
for (var i = expressions.length - 2; i >= 0; i--) { | ||
var left = expressions[i]; | ||
_result = makeNode('SeqOp', locationContainingNodes(left, lastExpression), { | ||
left: convertChild(left), | ||
right: _result | ||
}); | ||
} | ||
return _result; | ||
case 'Param': | ||
var param = convertChild(node.name); | ||
if (node.value) { | ||
return { | ||
v: makeNode('DefaultParam', node.locationData, { | ||
default: convertChild(node.value), | ||
param: param | ||
}) | ||
}; | ||
} | ||
} else { | ||
return convertChild(node.body); | ||
} | ||
if (node.splat) { | ||
return { | ||
v: makeNode('Rest', node.locationData, { | ||
expression: param | ||
}) | ||
}; | ||
} | ||
return { | ||
v: param | ||
}; | ||
case 'If': | ||
{ | ||
var condition = convertChild(node.condition); | ||
var consequent = convertChild(node.body); | ||
var alternate = convertChild(node.elseBody); | ||
var isUnless = false; | ||
if (consequent && consequent.range[0] < condition.range[0]) { | ||
// POST-if, so look for tokens between the consequent and the condition | ||
consequent.inline = true; | ||
var lastConsequentTokenIndex = context.sourceTokens.indexOfTokenEndingAtSourceIndex(consequent.range[1]); | ||
var firstConditionTokenIndex = context.sourceTokens.indexOfTokenStartingAtSourceIndex(condition.range[0]); | ||
for (var _i = lastConsequentTokenIndex; _i !== firstConditionTokenIndex; _i = _i.next()) { | ||
var token = context.sourceTokens.tokenAtIndex(_i); | ||
if (token.type === IF) { | ||
isUnless = source.slice(token.start, token.end) === 'unless'; | ||
break; | ||
} | ||
} | ||
case 'Block': | ||
if (node.expressions.length === 0) { | ||
return { | ||
v: null | ||
}; | ||
} else { | ||
// Regular `if`, so look at the start of the node. | ||
var _firstConditionTokenIndex = context.sourceTokens.indexOfTokenStartingAtSourceIndex(condition.range[0]); | ||
for (var _i2 = _firstConditionTokenIndex; _i2 !== null; _i2 = _i2.previous()) { | ||
var _token = context.sourceTokens.tokenAtIndex(_i2); | ||
if (_token.type === IF) { | ||
isUnless = source.slice(_token.start, _token.end) === 'unless'; | ||
var block = makeNode('Block', node.locationData, { | ||
statements: convertChild(node.expressions) | ||
}); | ||
block.inline = false; | ||
for (var _i3 = block.range[0] - 1; _i3 >= 0; _i3--) { | ||
var char = source[_i3]; | ||
if (char === '\n') { | ||
break; | ||
} else if (char !== ' ' && char !== '\t') { | ||
block.inline = true; | ||
break; | ||
} | ||
} | ||
return { | ||
v: block | ||
}; | ||
} | ||
return makeNode('Conditional', node.locationData, { | ||
isUnless: isUnless, | ||
condition: condition, | ||
consequent: consequent, | ||
alternate: alternate | ||
}); | ||
} | ||
case 'Bool': | ||
return { | ||
v: makeNode('Bool', node.locationData, { | ||
data: JSON.parse(node.val) | ||
}) | ||
}; | ||
case 'Code': | ||
var fnType = node.bound ? 'BoundFunction' : node.isGenerator ? 'GeneratorFunction' : 'Function'; | ||
return makeNode(fnType, node.locationData, { | ||
body: convertChild(node.body), | ||
parameters: convertChild(node.params) | ||
}); | ||
case 'Null': | ||
return { | ||
v: makeNode('Null', node.locationData) | ||
}; | ||
case 'Param': | ||
var param = convertChild(node.name); | ||
if (node.value) { | ||
return makeNode('DefaultParam', node.locationData, { | ||
default: convertChild(node.value), | ||
param: param | ||
}); | ||
} | ||
if (node.splat) { | ||
return makeNode('Rest', node.locationData, { | ||
expression: param | ||
}); | ||
} | ||
return param; | ||
case 'Undefined': | ||
return { | ||
v: makeNode('Undefined', node.locationData) | ||
}; | ||
case 'Block': | ||
if (node.expressions.length === 0) { | ||
return null; | ||
} else { | ||
var block = makeNode('Block', node.locationData, { | ||
statements: convertChild(node.expressions) | ||
case 'Return': | ||
return { | ||
v: makeNode('Return', node.locationData, { | ||
expression: node.expression ? convertChild(node.expression) : null | ||
}) | ||
}; | ||
case 'For': | ||
if (locationsEqual(node.body.locationData, node.locationData)) { | ||
node.body.locationData = locationContainingNodes.apply(undefined, toConsumableArray(node.body.expressions)); | ||
} | ||
if (node.object) { | ||
return { | ||
v: 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 { | ||
return { | ||
v: 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': | ||
var 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 | ||
}); | ||
block.inline = false; | ||
for (var _i3 = block.range[0] - 1; _i3 >= 0; _i3--) { | ||
var char = source[_i3]; | ||
if (char === '\n') { | ||
break; | ||
} else if (char !== ' ' && char !== '\t') { | ||
block.inline = true; | ||
break; | ||
} | ||
if (result.raw.indexOf('loop') === 0) { | ||
result.condition = { | ||
type: 'Bool', | ||
data: true, | ||
virtual: true | ||
}; | ||
} | ||
return block; | ||
} | ||
return { | ||
v: result | ||
}; | ||
case 'Bool': | ||
return makeNode('Bool', node.locationData, { | ||
data: JSON.parse(node.val) | ||
}); | ||
case 'Existence': | ||
return { | ||
v: makeNode('UnaryExistsOp', node.locationData, { | ||
expression: convertChild(node.expression) | ||
}) | ||
}; | ||
case 'Null': | ||
return makeNode('Null', node.locationData); | ||
case 'Class': | ||
var nameNode = node.variable ? convertChild(node.variable) : null; | ||
case 'Undefined': | ||
return makeNode('Undefined', node.locationData); | ||
var ctor = null; | ||
var boundMembers = []; | ||
var body = !node.body || node.body.expressions.length === 0 ? null : makeNode('Block', node.body.locationData, { | ||
statements: node.body.expressions.reduce(function (statements, expr) { | ||
if (type(expr) === 'Value' && type(expr.base) === 'Obj') { | ||
expr.base.properties.forEach(function (property) { | ||
var key = void 0; | ||
var value = void 0; | ||
switch (type(property)) { | ||
case 'Value': | ||
// shorthand property | ||
key = value = convertChild(property); | ||
break; | ||
case 'Return': | ||
return makeNode('Return', node.locationData, { | ||
expression: node.expression ? convertChild(node.expression) : null | ||
}); | ||
case 'Comment': | ||
return; | ||
case 'For': | ||
if (locationsEqual(node.body.locationData, node.locationData)) { | ||
node.body.locationData = locationContainingNodes.apply(undefined, toConsumableArray(node.body.expressions)); | ||
} | ||
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 | ||
default: | ||
key = convertChild(property.variable); | ||
value = convertChild(property.value); | ||
break; | ||
} | ||
if (key.data === 'constructor') { | ||
statements.push(ctor = makeNode('Constructor', property.locationData, { | ||
assignee: key, | ||
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; | ||
}, []) | ||
}); | ||
} 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': | ||
var 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 { | ||
v: makeNode('Class', node.locationData, { | ||
name: nameNode, | ||
nameAssignee: nameNode, | ||
body: body, | ||
boundMembers: boundMembers, | ||
parent: node.parent ? convertChild(node.parent) : null, | ||
ctor: ctor | ||
}) | ||
}; | ||
} | ||
return result; | ||
case 'Existence': | ||
return makeNode('UnaryExistsOp', node.locationData, { | ||
expression: convertChild(node.expression) | ||
}); | ||
case 'Switch': | ||
return { | ||
v: makeNode('Switch', node.locationData, { | ||
expression: convertChild(node.subject), | ||
cases: node.cases.map(function (_ref2) { | ||
var _ref3 = slicedToArray(_ref2, 2); | ||
case 'Class': | ||
var nameNode = node.variable ? convertChild(node.variable) : null; | ||
var conditions = _ref3[0]; | ||
var body = _ref3[1]; | ||
var ctor = null; | ||
var boundMembers = []; | ||
var body = !node.body || node.body.expressions.length === 0 ? null : makeNode('Block', node.body.locationData, { | ||
statements: node.body.expressions.reduce(function (statements, expr) { | ||
if (type(expr) === 'Value' && type(expr.base) === 'Obj') { | ||
expr.base.properties.forEach(function (property) { | ||
var key = void 0; | ||
var value = void 0; | ||
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 (!Array.isArray(conditions)) { | ||
conditions = [conditions]; | ||
} | ||
if (key.data === 'constructor') { | ||
statements.push(ctor = makeNode('Constructor', property.locationData, { | ||
assignee: key, | ||
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; | ||
}, []) | ||
}); | ||
var loc = expandLocationLeftThrough(locationContainingNodes(conditions[0], body), 'when '); | ||
return makeNode('SwitchCase', loc, { | ||
conditions: convertChild(conditions), | ||
consequent: convertChild(body) | ||
}); | ||
}).filter(function (node) { | ||
return node; | ||
}), | ||
alternate: convertChild(node.otherwise) | ||
}) | ||
}; | ||
return makeNode('Class', node.locationData, { | ||
name: nameNode, | ||
nameAssignee: nameNode, | ||
body: body, | ||
boundMembers: boundMembers, | ||
parent: node.parent ? convertChild(node.parent) : null, | ||
ctor: ctor | ||
}); | ||
case 'Splat': | ||
return { | ||
v: makeNode('Spread', node.locationData, { | ||
expression: convertChild(node.name) | ||
}) | ||
}; | ||
case 'Switch': | ||
return makeNode('Switch', node.locationData, { | ||
expression: convertChild(node.subject), | ||
cases: node.cases.map(function (_ref) { | ||
var _ref2 = slicedToArray(_ref, 2); | ||
case 'Throw': | ||
return { | ||
v: makeNode('Throw', node.locationData, { | ||
expression: convertChild(node.expression) | ||
}) | ||
}; | ||
var conditions = _ref2[0]; | ||
var body = _ref2[1]; | ||
case 'Try': | ||
return { | ||
v: makeNode('Try', node.locationData, { | ||
body: convertChild(node.attempt), | ||
catchAssignee: convertChild(node.errorVariable), | ||
catchBody: convertChild(node.recovery), | ||
finallyBody: convertChild(node.ensure) | ||
}) | ||
}; | ||
if (!Array.isArray(conditions)) { | ||
conditions = [conditions]; | ||
} | ||
var loc = expandLocationLeftThrough(locationContainingNodes(conditions[0], body), 'when '); | ||
return makeNode('SwitchCase', loc, { | ||
conditions: convertChild(conditions), | ||
consequent: convertChild(body) | ||
}); | ||
}).filter(function (node) { | ||
return node; | ||
}), | ||
alternate: convertChild(node.otherwise) | ||
}); | ||
case 'Range': | ||
return { | ||
v: makeNode('Range', node.locationData, { | ||
left: convertChild(node.from), | ||
right: convertChild(node.to), | ||
isInclusive: !node.exclusive | ||
}) | ||
}; | ||
case 'Splat': | ||
return makeNode('Spread', node.locationData, { | ||
expression: convertChild(node.name) | ||
}); | ||
case 'In': | ||
{ | ||
// We don't use the `negated` flag on `node` because it gets set to | ||
// `true` when a parent `If` is an `unless`. | ||
var _left = convertChild(node.object); | ||
var right = convertChild(node.array); | ||
var isNot = false; | ||
case 'Throw': | ||
return makeNode('Throw', node.locationData, { | ||
expression: convertChild(node.expression) | ||
}); | ||
var lastTokenIndexOfLeft = context.sourceTokens.indexOfTokenEndingAtSourceIndex(_left.range[1]); | ||
var firstTokenIndexOfRight = context.sourceTokens.indexOfTokenStartingAtSourceIndex(right.range[0]); | ||
case 'Try': | ||
return makeNode('Try', node.locationData, { | ||
body: convertChild(node.attempt), | ||
catchAssignee: convertChild(node.errorVariable), | ||
catchBody: convertChild(node.recovery), | ||
finallyBody: convertChild(node.ensure) | ||
}); | ||
for (var _i4 = lastTokenIndexOfLeft.next(); _i4 !== firstTokenIndexOfRight; _i4 = _i4.next()) { | ||
var _token2 = context.sourceTokens.tokenAtIndex(_i4); | ||
if (_token2.type === RELATION) { | ||
isNot = source.slice(_token2.start, _token2.end) !== 'in'; | ||
} | ||
} | ||
case 'Range': | ||
return makeNode('Range', node.locationData, { | ||
left: convertChild(node.from), | ||
right: convertChild(node.to), | ||
isInclusive: !node.exclusive | ||
}); | ||
case 'In': | ||
{ | ||
// We don't use the `negated` flag on `node` because it gets set to | ||
// `true` when a parent `If` is an `unless`. | ||
var _left = convertChild(node.object); | ||
var right = convertChild(node.array); | ||
var isNot = false; | ||
var lastTokenIndexOfLeft = context.sourceTokens.indexOfTokenEndingAtSourceIndex(_left.range[1]); | ||
var firstTokenIndexOfRight = context.sourceTokens.indexOfTokenStartingAtSourceIndex(right.range[0]); | ||
for (var _i4 = lastTokenIndexOfLeft.next(); _i4 !== firstTokenIndexOfRight; _i4 = _i4.next()) { | ||
var _token2 = context.sourceTokens.tokenAtIndex(_i4); | ||
if (_token2.type === RELATION) { | ||
isNot = source.slice(_token2.start, _token2.end) !== 'in'; | ||
} | ||
return { | ||
v: makeNode('InOp', node.locationData, { | ||
left: _left, | ||
right: right, | ||
isNot: isNot | ||
}) | ||
}; | ||
} | ||
return makeNode('InOp', node.locationData, { | ||
left: _left, | ||
right: right, | ||
isNot: isNot | ||
}); | ||
} | ||
case 'Expansion': | ||
return { | ||
v: makeNode('Expansion', node.locationData) | ||
}; | ||
case 'Expansion': | ||
return makeNode('Expansion', node.locationData); | ||
case 'Comment': | ||
return { | ||
v: null | ||
}; | ||
case 'Comment': | ||
return null; | ||
case 'Extends': | ||
return { | ||
v: makeNode('ExtendsOp', node.locationData, { | ||
left: convertChild(node.child), | ||
right: convertChild(node.parent) | ||
}) | ||
}; | ||
case 'Extends': | ||
return makeNode('ExtendsOp', node.locationData, { | ||
left: convertChild(node.child), | ||
right: convertChild(node.parent) | ||
}); | ||
default: | ||
throw new Error('unknown node type: ' + type(node) + '\n' + JSON.stringify(node, null, 2)); | ||
break; | ||
} | ||
}(); | ||
default: | ||
throw new Error('unknown node type: ' + type(node) + '\n' + JSON.stringify(node, null, 2)); | ||
break; | ||
} | ||
if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; | ||
function convertChild(child) { | ||
@@ -1719,14 +1659,14 @@ if (!child) { | ||
if (loc) { | ||
var _start = mapper(loc.first_line, loc.first_column); | ||
var _end = mapper(loc.last_line, loc.last_column) + 1; | ||
var start = mapper(loc.first_line, loc.first_column); | ||
var 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]; | ||
result.range = [start, end]; | ||
} else { | ||
result.virtual = true; | ||
} | ||
for (var key in attrs) { | ||
if (attrs.hasOwnProperty(key)) { | ||
var _value = attrs[key]; | ||
result[key] = _value; | ||
for (var _key2 in attrs) { | ||
if (attrs.hasOwnProperty(_key2)) { | ||
var _value = attrs[_key2]; | ||
result[_key2] = _value; | ||
if (_value && result.range) { | ||
@@ -1782,5 +1722,5 @@ (Array.isArray(_value) ? _value : [_value]).forEach(function (node) { | ||
function addElements(_ref3) { | ||
var left = _ref3.left; | ||
var right = _ref3.right; | ||
function addElements(_ref) { | ||
var left = _ref.left; | ||
var right = _ref.right; | ||
@@ -2046,15 +1986,15 @@ if (left.type === 'PlusOp') { | ||
var _result2 = makeNode(nodeType, op.locationData, { | ||
var _result = makeNode(nodeType, op.locationData, { | ||
left: convertNode(op.first, [].concat(toConsumableArray(ancestors), [op])), | ||
right: convertNode(op.second, [].concat(toConsumableArray(ancestors), [op])) | ||
}); | ||
if (_result2.type === 'InstanceofOp' || _result2.type === 'OfOp') { | ||
var _lastTokenIndexOfLeft = context.sourceTokens.indexOfTokenEndingAtSourceIndex(_result2.left.range[1]); | ||
var _firstTokenIndexOfRight = context.sourceTokens.indexOfTokenStartingAtSourceIndex(_result2.right.range[0]); | ||
var _isNot = false; | ||
if (_result.type === 'InstanceofOp' || _result.type === 'OfOp') { | ||
var lastTokenIndexOfLeft = context.sourceTokens.indexOfTokenEndingAtSourceIndex(_result.left.range[1]); | ||
var firstTokenIndexOfRight = context.sourceTokens.indexOfTokenStartingAtSourceIndex(_result.right.range[0]); | ||
var isNot = false; | ||
for (var _i5 = _lastTokenIndexOfLeft.next(); _i5 !== _firstTokenIndexOfRight; _i5 = _i5.next()) { | ||
var _token3 = context.sourceTokens.tokenAtIndex(_i5); | ||
if (_token3.type === OPERATOR || _token3.type === RELATION) { | ||
_isNot = source.slice(_token3.start, _token3.start + 'not'.length) === 'not'; | ||
for (var i = lastTokenIndexOfLeft.next(); i !== firstTokenIndexOfRight; i = i.next()) { | ||
var token = context.sourceTokens.tokenAtIndex(i); | ||
if (token.type === OPERATOR || token.type === RELATION) { | ||
isNot = source.slice(token.start, token.start + 'not'.length) === 'not'; | ||
break; | ||
@@ -2064,5 +2004,5 @@ } | ||
_result2.isNot = _isNot; | ||
_result.isNot = isNot; | ||
} | ||
return _result2; | ||
return _result; | ||
} else { | ||
@@ -2069,0 +2009,0 @@ switch (op.operator) { |
{ | ||
"name": "decaffeinate-parser", | ||
"version": "1.3.1", | ||
"description": "A better AST for CoffeeScript, inspired by CoffeeScriptRedux.", | ||
@@ -11,3 +10,4 @@ "main": "dist/decaffeinate-parser.umd.js", | ||
"prepublish": "npm run build", | ||
"test": "mocha" | ||
"test": "mocha", | ||
"semantic-release": "semantic-release pre && npm publish && semantic-release post" | ||
}, | ||
@@ -25,3 +25,2 @@ "keywords": [ | ||
"dependencies": { | ||
"binary-search": "^1.2.0", | ||
"coffee-lex": "^1.4.1", | ||
@@ -37,5 +36,7 @@ "coffee-script": "^1.10.0" | ||
"coffee-script-redux": "^2.0.0-beta8", | ||
"cz-conventional-changelog": "^1.1.6", | ||
"mocha": "^2.4.5", | ||
"rollup": "^0.30.0", | ||
"rollup": "^0.32.0", | ||
"rollup-plugin-babel": "^2.4.0", | ||
"semantic-release": "^4.3.5", | ||
"string-repeat": "^1.1.1" | ||
@@ -45,3 +46,13 @@ }, | ||
"registry": "https://registry.npmjs.org/" | ||
} | ||
} | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/decaffeinate/decaffeinate-parser.git" | ||
}, | ||
"config": { | ||
"commitizen": { | ||
"path": "./node_modules/cz-conventional-changelog" | ||
} | ||
}, | ||
"version": "2.0.0" | ||
} |
Sorry, the diff of this file is too big to display
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
2
0
134353
12
3607
- Removedbinary-search@^1.2.0
- Removedbinary-search@1.3.6(transitive)