Functions from remix-lib: - sourceLocationFromAstNode - nodesAtPosition - getLinebreakPositions - findNodeAtSourceLocation Add walkFull that traverses more of the AST nodes. This is non-legacy only.pull/7/head
parent
7cbc24e0d3
commit
40168bd26d
@ -1,2 +1,3 @@ |
||||
export * from './types' |
||||
export * from './astWalker' |
||||
export * from './sourceMappings' |
||||
|
@ -0,0 +1,105 @@ |
||||
import { AstWalker } from './astWalker'; |
||||
import { AstNode, Location } from "./index"; |
||||
|
||||
/** |
||||
* Break out fields of an AST's "src" attribute string (s:l:f) |
||||
* into its "start", "length", and "file index" components. |
||||
* |
||||
* @param {AstNode} astNode - the object to convert. |
||||
*/ |
||||
export function sourceLocationFromAstNode(astNode: AstNode): Location | null { |
||||
if (astNode.src) { |
||||
var split = astNode.src.split(':') |
||||
return <Location>{ |
||||
start: parseInt(split[0], 10), |
||||
length: parseInt(split[1], 10), |
||||
file: parseInt(split[2], 10) |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Routines for retrieving AST object(s) using some criteria, usually |
||||
* includng "src' information. |
||||
*/ |
||||
export class SourceMappings { |
||||
|
||||
readonly source: string; |
||||
readonly lineBreaks: Array<number>; |
||||
|
||||
constructor(source: string) { |
||||
this.source = source; |
||||
this.lineBreaks = this.getLinebreakPositions(); |
||||
}; |
||||
|
||||
/** |
||||
* get a list of nodes that are at the given @arg offset |
||||
* |
||||
* @param {String} astNodeType - type of node to return or null |
||||
* @param {Int} position - character offset |
||||
* @return {Object} ast object given by the compiler |
||||
*/ |
||||
nodesAtPosition(astNodeType: string | null, position: number, ast: AstNode): Array<AstNode> { |
||||
const astWalker = new AstWalker() |
||||
const callback = {} |
||||
let found: Array<AstNode> = []; |
||||
|
||||
/* FIXME: Looking at AST walker code, |
||||
I don't understand a need to return a boolean. */ |
||||
callback['*'] = function(node: AstNode): boolean { |
||||
let nodeLocation = sourceLocationFromAstNode(node); |
||||
if (nodeLocation && |
||||
nodeLocation.start <= position && |
||||
nodeLocation.start + nodeLocation.length >= position) { |
||||
if (!astNodeType || astNodeType === node.name) { |
||||
found.push(node) |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
astWalker.walk(ast, callback); |
||||
return found; |
||||
} |
||||
|
||||
/** |
||||
* Retrieve line/column position of each source char |
||||
* |
||||
* @param {String} source - contract source code |
||||
* @return {Array} returns an array containing offset of line breaks |
||||
*/ |
||||
getLinebreakPositions(source: string = this.source): Array<number> { |
||||
let ret: Array<number> = []; |
||||
for (var pos = source.indexOf('\n'); pos >= 0; pos = source.indexOf('\n', pos + 1)) { |
||||
ret.push(pos) |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
findNodeAtSourceLocation(astNodeType: string, sourceLocation: Location, ast: AstNode | null) { |
||||
const astWalker = new AstWalker() |
||||
const callback = {}; |
||||
let found = null; |
||||
/* FIXME: Looking at AST walker code, |
||||
I don't understand a need to return a boolean. */ |
||||
callback['*'] = function(node: AstNode) { |
||||
let nodeLocation = sourceLocationFromAstNode(node); |
||||
if (nodeLocation && |
||||
nodeLocation.start <= sourceLocation.start && |
||||
nodeLocation.start + nodeLocation.length >= sourceLocation.start + sourceLocation.length) { |
||||
if (astNodeType === node.nodeType) { |
||||
found = node; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
astWalker.walkFull(ast, callback); |
||||
return found; |
||||
} |
||||
} |
||||
|
||||
module.exports = { |
||||
SourceMappings, |
||||
sourceLocationFromAstNode |
||||
}; |
@ -0,0 +1,26 @@ |
||||
import tape from "tape"; |
||||
import { SourceMappings, sourceLocationFromAstNode } from "../src/sourceMappings"; |
||||
import node from "./resources/newAST"; |
||||
|
||||
tape("SourceMappings", (t: tape.Test) => { |
||||
const source = node.source; |
||||
const srcMappings = new SourceMappings(source); |
||||
t.test("SourceMappings constructor", (st: tape.Test) => { |
||||
st.plan(2); |
||||
|
||||
st.equal(source, srcMappings.source); |
||||
st.deepEqual(srcMappings.lineBreaks, |
||||
[15, 26, 27, 38, 39, 81, 87, 103, 119, 135, 141, 142, 186, 192, 193, 199]); |
||||
st.end(); |
||||
}); |
||||
t.test("SourceMappings fns", (st: tape.Test) => { |
||||
st.plan(2); |
||||
const ast = node.ast; |
||||
st.deepEqual(sourceLocationFromAstNode(ast.nodes[0]), |
||||
{ start: 0, length: 31, file: 0 }); |
||||
const loc = { start: 267, length: 20, file: 0 }; |
||||
const rr = srcMappings.findNodeAtSourceLocation('ExpressionStatement', loc, ast); |
||||
st.ok(rr); |
||||
st.end(); |
||||
}); |
||||
}); |
Loading…
Reference in new issue