commit
3cd9cd2996
@ -1,2 +1,3 @@ |
|||||||
export * from './types' |
export * from './types' |
||||||
export * from './astWalker' |
export * from './astWalker' |
||||||
|
export * from './sourceMappings' |
||||||
|
@ -0,0 +1,93 @@ |
|||||||
|
import { isAstNode, AstWalker } from './astWalker'; |
||||||
|
import { AstNode, Location } from "./types"; |
||||||
|
|
||||||
|
export declare interface SourceMappings { |
||||||
|
new(): SourceMappings; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 (isAstNode(astNode) && 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; |
||||||
|
|
||||||
|
// Create a list of line offsets which will be used to map between
|
||||||
|
// character offset and line/column positions.
|
||||||
|
let lineBreaks: Array<number> = []; |
||||||
|
for (var pos = source.indexOf('\n'); pos >= 0; pos = source.indexOf('\n', pos + 1)) { |
||||||
|
lineBreaks.push(pos) |
||||||
|
} |
||||||
|
this.lineBreaks = lineBreaks; |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* get a list of nodes that are at the given @arg position |
||||||
|
* |
||||||
|
* @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: Location, ast: AstNode): Array<AstNode> { |
||||||
|
const astWalker = new AstWalker() |
||||||
|
let found: Array<AstNode> = []; |
||||||
|
|
||||||
|
const callback = function(node: AstNode): boolean { |
||||||
|
let nodeLocation = sourceLocationFromAstNode(node); |
||||||
|
if (nodeLocation && |
||||||
|
nodeLocation.start == position.start && |
||||||
|
nodeLocation.length == position.length) { |
||||||
|
if (!astNodeType || astNodeType === node.nodeType) { |
||||||
|
found.push(node) |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
astWalker.walkFull(ast, callback); |
||||||
|
return found; |
||||||
|
} |
||||||
|
|
||||||
|
findNodeAtSourceLocation(astNodeType: string | undefined, sourceLocation: Location, ast: AstNode | null): AstNode | null { |
||||||
|
const astWalker = new AstWalker() |
||||||
|
let found = null; |
||||||
|
/* FIXME: Looking at AST walker code, |
||||||
|
I don't understand a need to return a boolean. */ |
||||||
|
const callback = function(node: AstNode) { |
||||||
|
let nodeLocation = sourceLocationFromAstNode(node); |
||||||
|
if (nodeLocation && |
||||||
|
nodeLocation.start == sourceLocation.start && |
||||||
|
nodeLocation.length == sourceLocation.length) { |
||||||
|
if (astNodeType == undefined || astNodeType === node.nodeType) { |
||||||
|
found = node; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
astWalker.walkFull(ast, callback); |
||||||
|
return found; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
import tape from "tape"; |
||||||
|
import { AstNode, isAstNode, SourceMappings, sourceLocationFromAstNode } from "../src"; |
||||||
|
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(srcMappings.source, source, "sourceMappings object has source-code string"); |
||||||
|
st.deepEqual(srcMappings.lineBreaks, |
||||||
|
[15, 26, 27, 38, 39, 81, 87, 103, 119, 135, 141, 142, 186, 192, 193, 199], |
||||||
|
"sourceMappings has line-break offsets"); |
||||||
|
st.end(); |
||||||
|
}); |
||||||
|
t.test("SourceMappings functions", (st: tape.Test) => { |
||||||
|
// st.plan(2)
|
||||||
|
const ast = node.ast; |
||||||
|
st.deepEqual(sourceLocationFromAstNode(ast.nodes[0]), |
||||||
|
{ start: 0, length: 31, file: 0 }, |
||||||
|
"sourceLocationFromAstNode extracts a location"); |
||||||
|
|
||||||
|
/* Typescript will keep us from calling sourceLocationFromAstNode |
||||||
|
with the wrong type. However, for non-typescript uses, we add |
||||||
|
this test which casts to an AST to check that there is a |
||||||
|
run-time check in walkFull. |
||||||
|
*/ |
||||||
|
st.notOk(sourceLocationFromAstNode(<AstNode>null), |
||||||
|
"sourceLocationFromAstNode rejects an invalid astNode"); |
||||||
|
const loc = { start: 267, length: 20, file: 0 }; |
||||||
|
let astNode = srcMappings.findNodeAtSourceLocation('ExpressionStatement', loc, ast); |
||||||
|
st.ok(isAstNode(astNode), "findsNodeAtSourceLocation finds something"); |
||||||
|
astNode = srcMappings.findNodeAtSourceLocation('NotARealThingToFind', loc, ast); |
||||||
|
st.notOk(isAstNode(astNode), |
||||||
|
"findsNodeAtSourceLocation fails to find something when it should"); |
||||||
|
let astNodes = srcMappings.nodesAtPosition(null, loc, ast); |
||||||
|
st.equal(astNodes.length, 2, "nodesAtPosition should find more than one astNode"); |
||||||
|
st.ok(isAstNode(astNodes[0]), "nodesAtPosition returns only AST nodes"); |
||||||
|
// console.log(astNodes[0]);
|
||||||
|
astNodes = srcMappings.nodesAtPosition("ExpressionStatement", loc, ast); |
||||||
|
st.equal(astNodes.length, 1, "nodesAtPosition filtered to a single nodeType"); |
||||||
|
st.end(); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue