UNPKG

php-parser

Version:

Parse PHP code from JS and returns its AST

592 lines (577 loc) 17.3 kB
/** * Copyright (C) 2018 Glayzzle (BSD3 License) * @authors https://github.com/glayzzle/php-parser/graphs/contributors * @url http://glayzzle.com */ "use strict"; const Location = require("./ast/location"); const Position = require("./ast/position"); /** * ## Class hierarchy * * - [Location](#location) * - [Position](#position) * - [Node](#node) * - [Noop](#noop) * - [NullKeyword](#nullkeyword) * - [StaticVariable](#staticvariable) * - [EncapsedPart](#encapsedpart) * - [Constant](#constant) * - [Identifier](#identifier) * - [Reference](#reference) * - [TypeReference](#typereference) * - [ParentReference](#parentreference) * - [StaticReference](#staticreference) * - [SelfReference](#selfreference) * - [Name](#name) * - [TraitUse](#traituse) * - [TraitAlias](#traitalias) * - [TraitPrecedence](#traitprecedence) * - [Comment](#comment) * - [CommentLine](#commentline) * - [CommentBlock](#commentblock) * - [Error](#error) * - [Expression](#expression) * - [Entry](#entry) * - [ArrowFunc](#arrowfunc) * - [Closure](#closure) * - [ByRef](#byref) * - [Silent](#silent) * - [RetIf](#retif) * - [New](#new) * - [Include](#include) * - [Call](#call) * - [Eval](#eval) * - [Exit](#exit) * - [Clone](#clone) * - [Assign](#assign) * - [AssignRef](#assignref) * - [Array](#array) * - [List](#list) * - [Variable](#variable) * - [Variadic](#variadic) * - [Yield](#yield) * - [YieldFrom](#yieldfrom) * - [Print](#print) * - [Isset](#isset) * - [Empty](#empty) * - [Lookup](#lookup) * - [PropertyLookup](#propertylookup) * - [StaticLookup](#staticlookup) * - [OffsetLookup](#offsetlookup) * - [Operation](#operation) * - [Pre](#pre) * - [Post](#post) * - [Bin](#bin) * - [Unary](#unary) * - [Cast](#cast) * - [Literal](#literal) * - [Boolean](#boolean) * - [String](#string) * - [Number](#number) * - [Inline](#inline) * - [Magic](#magic) * - [Nowdoc](#nowdoc) * - [Encapsed](#encapsed) * - [Statement](#statement) * - [ConstantStatement](#constantstatement) * - [ClassConstant](#classconstant) * - [Return](#return) * - [Label](#label) * - [Continue](#continue) * - [Case](#case) * - [Break](#break) * - [Echo](#echo) * - [Unset](#unset) * - [Halt](#halt) * - [Declare](#declare) * - [Global](#global) * - [Static](#static) * - [If](#if) * - [Do](#do) * - [While](#while) * - [For](#for) * - [Foreach](#foreach) * - [Switch](#switch) * - [Goto](#goto) * - [Try](#try) * - [Catch](#catch) * - [Throw](#throw) * - [UseGroup](#usegroup) * - [UseItem](#useitem) * - [Block](#block) * - [Program](#program) * - [Namespace](#namespace) * - [PropertyStatement](#propertystatement) * - [Property](#property) * - [Declaration](#declaration) * - [Class](#class) * - [Interface](#interface) * - [Trait](#trait) * - [Function](#function) * - [Method](#method) * - [Parameter](#parameter) * --- */ /** * The AST builder class * @constructor AST * @memberOf module:php-parser * @tutorial AST * @property {Boolean} withPositions - Should locate any node (by default false) * @property {Boolean} withSource - Should extract the node original code (by default false) */ const AST = function (withPositions, withSource) { this.withPositions = withPositions; this.withSource = withSource; }; // operators in ascending order of precedence AST.precedence = {}; [ ["or"], ["xor"], ["and"], ["="], ["?"], ["??"], ["||"], ["&&"], ["|"], ["^"], ["&"], ["==", "!=", "===", "!==", /* '<>', */ "<=>"], ["<", "<=", ">", ">="], ["<<", ">>"], ["+", "-", "."], ["*", "/", "%"], ["!"], ["instanceof"], ["cast", "silent"], ["**"], // TODO: [ (array) // TODO: clone, new ].forEach(function (list, index) { list.forEach(function (operator) { AST.precedence[operator] = index + 1; }); }); /** * @private * @function AST#isRightAssociative * @memberOf module:php-parser * @param operator * @return {boolean} */ AST.prototype.isRightAssociative = function (operator) { return operator === "**" || operator === "??"; }; /** * Change parent node informations after swapping childs * @private * @function AST#swapLocations * @memberOf module:php-parser */ AST.prototype.swapLocations = function (target, first, last, parser) { if (this.withPositions) { target.loc.start = first.loc.start; target.loc.end = last.loc.end; if (this.withSource) { target.loc.source = parser.lexer._input.substring( target.loc.start.offset, target.loc.end.offset, ); } } }; /** * Includes locations from first & last into the target * @private * @function AST#resolveLocations * @memberOf module:php-parser */ AST.prototype.resolveLocations = function (target, first, last, parser) { if (this.withPositions) { if (target.loc.start.offset > first.loc.start.offset) { target.loc.start = first.loc.start; } /* istanbul ignore next */ if (target.loc.end.offset < last.loc.end.offset) { target.loc.end = last.loc.end; } if (this.withSource) { target.loc.source = parser.lexer._input.substring( target.loc.start.offset, target.loc.end.offset, ); } } }; /** * Check and fix precence, by default using right * @private * @function AST#resolvePrecedence * @memberOf module:php-parser */ AST.prototype.resolvePrecedence = function (result, parser) { let buffer, lLevel, rLevel; // handling precendence if (result.kind === "call") { // including what argument into location this.resolveLocations(result, result.what, result, parser); } else if ( result.kind === "propertylookup" || result.kind === "staticlookup" || (result.kind === "offsetlookup" && result.offset) ) { // including what argument into location this.resolveLocations(result, result.what, result.offset, parser); } else if (result.kind === "bin") { if (result.right && !result.right.parenthesizedExpression) { if (result.right.kind === "bin") { lLevel = AST.precedence[result.type]; rLevel = AST.precedence[result.right.type]; if ( lLevel && rLevel && rLevel <= lLevel && (result.type !== result.right.type || !this.isRightAssociative(result.type)) ) { // https://github.com/glayzzle/php-parser/issues/79 // shift precedence buffer = result.right; result.right = result.right.left; this.swapLocations(result, result.left, result.right, parser); buffer.left = this.resolvePrecedence(result, parser); this.swapLocations(buffer, buffer.left, buffer.right, parser); result = buffer; } } else if (result.right.kind === "retif") { lLevel = AST.precedence[result.type]; rLevel = AST.precedence["?"]; if (lLevel && rLevel && rLevel <= lLevel) { buffer = result.right; result.right = result.right.test; this.swapLocations(result, result.left, result.right, parser); buffer.test = this.resolvePrecedence(result, parser); this.swapLocations(buffer, buffer.test, buffer.falseExpr, parser); result = buffer; } } } } else if ( (result.kind === "silent" || result.kind === "cast") && result.expr && !result.expr.parenthesizedExpression ) { // https://github.com/glayzzle/php-parser/issues/172 if (result.expr.kind === "bin") { buffer = result.expr; result.expr = result.expr.left; this.swapLocations(result, result, result.expr, parser); buffer.left = this.resolvePrecedence(result, parser); this.swapLocations(buffer, buffer.left, buffer.right, parser); result = buffer; } else if (result.expr.kind === "retif") { buffer = result.expr; result.expr = result.expr.test; this.swapLocations(result, result, result.expr, parser); buffer.test = this.resolvePrecedence(result, parser); this.swapLocations(buffer, buffer.test, buffer.falseExpr, parser); result = buffer; } } else if (result.kind === "unary") { // https://github.com/glayzzle/php-parser/issues/75 if (result.what && !result.what.parenthesizedExpression) { // unary precedence is always lower if (result.what.kind === "bin") { buffer = result.what; result.what = result.what.left; this.swapLocations(result, result, result.what, parser); buffer.left = this.resolvePrecedence(result, parser); this.swapLocations(buffer, buffer.left, buffer.right, parser); result = buffer; } else if (result.what.kind === "retif") { buffer = result.what; result.what = result.what.test; this.swapLocations(result, result, result.what, parser); buffer.test = this.resolvePrecedence(result, parser); this.swapLocations(buffer, buffer.test, buffer.falseExpr, parser); result = buffer; } } } else if (result.kind === "retif") { // https://github.com/glayzzle/php-parser/issues/77 if ( result.falseExpr && result.falseExpr.kind === "retif" && !result.falseExpr.parenthesizedExpression ) { buffer = result.falseExpr; result.falseExpr = buffer.test; this.swapLocations(result, result.test, result.falseExpr, parser); buffer.test = this.resolvePrecedence(result, parser); this.swapLocations(buffer, buffer.test, buffer.falseExpr, parser); result = buffer; } } else if (result.kind === "assign") { // https://github.com/glayzzle/php-parser/issues/81 if ( result.right && result.right.kind === "bin" && !result.right.parenthesizedExpression ) { lLevel = AST.precedence["="]; rLevel = AST.precedence[result.right.type]; // only shifts with and, xor, or if (lLevel && rLevel && rLevel < lLevel) { buffer = result.right; result.right = result.right.left; buffer.left = result; this.swapLocations(buffer, buffer.left, result.right, parser); result = buffer; } } } else if (result.kind === "expressionstatement") { this.swapLocations(result, result.expression, result, parser); } return result; }; /** * Prepares an AST node * @private * @function AST#prepare * @memberOf module:php-parser * @param {String|null} kind - Defines the node type * @param {*} docs - (if null, the kind must be passed at the function call) * @param {Parser} parser - The parser instance (use for extracting locations) * @return {Function} */ AST.prototype.prepare = function (kind, docs, parser) { let start = null; if (this.withPositions || this.withSource) { start = parser.position(); } const self = this; // returns the node const result = function () { let location = null; const args = Array.prototype.slice.call(arguments); args.push(docs); if (self.withPositions || self.withSource) { let src = null; if (self.withSource) { src = parser.lexer._input.substring(start.offset, parser.prev[2]); } // if with source, need location on swapLocations function location = new Location( src, start, new Position(parser.prev[0], parser.prev[1], parser.prev[2]), ); // last argument is always the location args.push(location); } // handle lazy kind definitions if (!kind) { kind = args.shift(); } // build the object const node = self[kind]; if (typeof node !== "function") { throw new Error('Undefined node "' + kind + '"'); } const astNode = Object.create(node.prototype); node.apply(astNode, args); result.instance = astNode; /* istanbul ignore next */ if (result.trailingComments) { // buffer of trailingComments astNode.trailingComments = result.trailingComments; } if (typeof result.postBuild === "function") { result.postBuild(astNode); } if (parser.debug) { delete self.stack[result.stackUid]; } return self.resolvePrecedence(astNode, parser); }; if (parser.debug) { if (!this.stack) { this.stack = {}; this.stackUid = 1; } this.stack[++this.stackUid] = { position: start, stack: new Error().stack.split("\n").slice(3, 5), }; result.stackUid = this.stackUid; } /** * Sets a list of trailing comments * @private * @param {*} docs */ result.setTrailingComments = function (docs) { if (result.instance) { // already created result.instance.setTrailingComments(docs); } else { result.trailingComments = docs; } }; /** * Release a node without using it on the AST * @private * @param {*} target */ result.destroy = function (target) { if (docs) { // release current docs stack if (target) { if (!target.leadingComments) { target.leadingComments = docs; } else { target.leadingComments = docs.concat(target.leadingComments); } } else { parser._docIndex = parser._docs.length - docs.length; } } if (parser.debug) { delete self.stack[result.stackUid]; } }; return result; }; AST.prototype.checkNodes = function () { const errors = []; for (const k in this.stack) { if (Object.prototype.hasOwnProperty.call(this.stack, k)) { this.stack[k].key = k; errors.push(this.stack[k]); } } this.stack = {}; return errors; }; // Define all AST nodes [ require("./ast/array"), require("./ast/arrowfunc"), require("./ast/assign"), require("./ast/assignref"), require("./ast/attribute"), require("./ast/attrgroup"), require("./ast/bin"), require("./ast/block"), require("./ast/boolean"), require("./ast/break"), require("./ast/byref"), require("./ast/call"), require("./ast/case"), require("./ast/cast"), require("./ast/catch"), require("./ast/class"), require("./ast/classconstant"), require("./ast/clone"), require("./ast/closure"), require("./ast/comment"), require("./ast/commentblock"), require("./ast/commentline"), require("./ast/constant"), require("./ast/constantstatement"), require("./ast/continue"), require("./ast/declaration"), require("./ast/declare"), require("./ast/declaredirective"), require("./ast/do"), require("./ast/echo"), require("./ast/empty"), require("./ast/encapsed"), require("./ast/encapsedpart"), require("./ast/entry"), require("./ast/enum"), require("./ast/enumcase"), require("./ast/error"), require("./ast/eval"), require("./ast/exit"), require("./ast/expression"), require("./ast/expressionstatement"), require("./ast/for"), require("./ast/foreach"), require("./ast/function"), require("./ast/global"), require("./ast/goto"), require("./ast/halt"), require("./ast/identifier"), require("./ast/if"), require("./ast/include"), require("./ast/inline"), require("./ast/interface"), require("./ast/intersectiontype"), require("./ast/isset"), require("./ast/label"), require("./ast/list"), require("./ast/literal"), require("./ast/lookup"), require("./ast/magic"), require("./ast/match"), require("./ast/matcharm"), require("./ast/method"), require("./ast/name"), require("./ast/namespace"), require("./ast/namedargument"), require("./ast/new"), require("./ast/node"), require("./ast/noop"), require("./ast/nowdoc"), require("./ast/nullkeyword"), require("./ast/nullsafepropertylookup"), require("./ast/number"), require("./ast/offsetlookup"), require("./ast/operation"), require("./ast/parameter"), require("./ast/parentreference"), require("./ast/post"), require("./ast/pre"), require("./ast/print"), require("./ast/program"), require("./ast/property"), require("./ast/propertylookup"), require("./ast/propertystatement"), require("./ast/reference"), require("./ast/retif"), require("./ast/return"), require("./ast/selfreference"), require("./ast/silent"), require("./ast/statement"), require("./ast/static"), require("./ast/staticvariable"), require("./ast/staticlookup"), require("./ast/staticreference"), require("./ast/string"), require("./ast/switch"), require("./ast/throw"), require("./ast/trait"), require("./ast/traitalias"), require("./ast/traitprecedence"), require("./ast/traituse"), require("./ast/try"), require("./ast/typereference"), require("./ast/unary"), require("./ast/uniontype"), require("./ast/unset"), require("./ast/usegroup"), require("./ast/useitem"), require("./ast/variable"), require("./ast/variadic"), require("./ast/variadicplaceholder"), require("./ast/while"), require("./ast/yield"), require("./ast/yieldfrom"), ].forEach(function (ctor) { AST.prototype[ctor.kind] = ctor; }); module.exports = AST;