Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
B
baas-ide
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
guxukai
baas-ide
Commits
a600b30a
Unverified
Commit
a600b30a
authored
Jun 18, 2019
by
Omkara
Committed by
GitHub
Jun 18, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1216 from rocky/ast-functions
Add routines for finding AST nodes
parents
3e4cd345
c87ba46f
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
232 additions
and
10 deletions
+232
-10
.gitignore
.gitignore
+2
-0
astWalker.ts
remix-astwalker/src/astWalker.ts
+48
-0
index.ts
remix-astwalker/src/index.ts
+1
-0
sourceMappings.ts
remix-astwalker/src/sourceMappings.ts
+93
-0
types.ts
remix-astwalker/src/types.ts
+15
-6
newTests.ts
remix-astwalker/tests/newTests.ts
+29
-4
newAST.ts
remix-astwalker/tests/resources/newAST.ts
+0
-0
sourceMappings.ts
remix-astwalker/tests/sourceMappings.ts
+44
-0
No files found.
.gitignore
View file @
a600b30a
...
...
@@ -14,3 +14,5 @@ package-lock.json
TODO
soljson.js
lerna-debug.log
*~
/tmp
remix-astwalker/src/astWalker.ts
View file @
a600b30a
...
...
@@ -4,6 +4,21 @@ import { AstNodeLegacy, Node, AstNode } from "./index";
export
declare
interface
AstWalker
{
new
():
EventEmitter
;
}
const
isObject
=
function
(
obj
:
any
):
boolean
{
return
obj
!=
null
&&
obj
.
constructor
.
name
===
"Object"
}
export
function
isAstNode
(
node
:
Object
):
boolean
{
return
(
isObject
(
node
)
&&
'id'
in
node
&&
'nodeType'
in
node
&&
'src'
in
node
)
}
/**
* Crawl the given AST through the function walk(ast, callback)
*/
...
...
@@ -22,6 +37,10 @@ export class AstWalker extends EventEmitter {
node
:
AstNodeLegacy
|
AstNode
,
callback
:
Object
|
Function
):
any
{
// FIXME: we shouldn't be doing this callback determination type on each AST node,
// since the callback function is set once per walk.
// Better would be to store the right one as a variable and
// return that.
if
(
<
AstNodeLegacy
>
node
)
{
if
((
<
AstNodeLegacy
>
node
).
name
in
callback
)
{
return
callback
[(
<
AstNodeLegacy
>
node
).
name
](
node
);
...
...
@@ -98,6 +117,35 @@ export class AstWalker extends EventEmitter {
}
}
walkFullInternal
(
ast
:
AstNode
,
callback
:
Function
)
{
if
(
isAstNode
(
ast
))
{
// console.log(`XXX id ${ast.id}, nodeType: ${ast.nodeType}, src: ${ast.src}`);
callback
(
ast
);
for
(
let
k
of
Object
.
keys
(
ast
))
{
// Possible optimization:
// if (k in ['id', 'src', 'nodeType']) continue;
const
astItem
=
ast
[
k
];
if
(
Array
.
isArray
(
astItem
))
{
for
(
let
child
of
astItem
)
{
if
(
child
)
{
this
.
walkFullInternal
(
child
,
callback
);
}
}
}
else
{
this
.
walkFullInternal
(
astItem
,
callback
);
}
}
}
}
// Normalizes parameter callback and calls walkFullInternal
walkFull
(
ast
:
AstNode
,
callback
:
any
)
{
if
(
!
isAstNode
(
ast
))
throw
new
TypeError
(
"first argument should be an ast"
);
return
this
.
walkFullInternal
(
ast
,
callback
);
}
walkAstList
(
sourcesList
:
Node
,
cb
?:
Function
)
{
if
(
cb
)
{
if
(
sourcesList
.
ast
)
{
...
...
remix-astwalker/src/index.ts
View file @
a600b30a
export
*
from
'./types'
export
*
from
'./astWalker'
export
*
from
'./sourceMappings'
remix-astwalker/src/sourceMappings.ts
0 → 100644
View file @
a600b30a
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
;
}
}
remix-astwalker/src/types.ts
View file @
a600b30a
export
interface
Location
{
start
:
number
;
length
:
number
;
file
:
number
;
// Would it be clearer to call this a file index?
}
export
interface
Node
{
ast
?:
AstNode
;
legacyAST
?:
AstNodeLegacy
;
...
...
@@ -6,12 +12,15 @@ export interface Node {
}
export
interface
AstNode
{
/* The following fields are essential, and indicates an that object
is an AST node. */
id
:
number
;
// This is unique across all nodes in an AST tree
nodeType
:
string
;
src
:
string
;
absolutePath
?:
string
;
exportedSymbols
?:
Object
;
id
:
number
;
nodeType
:
string
;
nodes
?:
Array
<
AstNode
>
;
src
:
string
;
literals
?:
Array
<
string
>
;
file
?:
string
;
scope
?:
number
;
...
...
@@ -21,10 +30,10 @@ export interface AstNode {
}
export
interface
AstNodeLegacy
{
id
:
number
;
name
:
string
;
id
:
number
;
// This is unique across all nodes in an AST tree
name
:
string
;
// This corresponds to "nodeType" in ASTNode
src
:
string
;
children
?:
Array
<
AstNodeLegacy
>
;
children
?:
Array
<
AstNodeLegacy
>
;
// This corresponds to "nodes" in ASTNode
attributes
?:
AstNodeAtt
;
}
...
...
remix-astwalker/tests/newTests.ts
View file @
a600b30a
import
tape
from
"tape"
;
import
{
AstWalker
,
AstNode
}
from
"../src"
;
import
{
AstWalker
,
AstNode
,
isAstNode
}
from
"../src"
;
import
node
from
"./resources/newAST"
;
import
legacyNode
from
"./resources/legacyAST"
;
tape
(
"New ASTWalker"
,
(
t
:
tape
.
Test
)
=>
{
t
.
test
(
"ASTWalker.walk && .walkAST"
,
(
st
:
tape
.
Test
)
=>
{
// New Ast Object
const
astWalker
=
new
AstWalker
();
t
.
test
(
"ASTWalker.walk && .walkastList"
,
(
st
:
tape
.
Test
)
=>
{
st
.
plan
(
24
);
// New Ast Object
const
astWalker
=
new
AstWalker
();
// EventListener
astWalker
.
on
(
"node"
,
node
=>
{
if
(
node
.
nodeType
===
"ContractDefinition"
)
{
...
...
@@ -51,6 +52,30 @@ tape("New ASTWalker", (t: tape.Test) => {
});
st
.
end
();
});
t
.
test
(
"ASTWalkFull"
,
(
st
:
tape
.
Test
)
=>
{
const
astNodeCount
=
26
;
st
.
plan
(
2
+
astNodeCount
);
let
count
:
number
=
0
;
astWalker
.
walkFull
(
node
.
ast
,
(
node
:
AstNode
)
=>
{
st
.
ok
(
isAstNode
(
node
),
"passed an ast node"
);
count
+=
1
;
});
st
.
equal
(
count
,
astNodeCount
,
"traverses all AST nodes"
);
count
=
0
;
let
badCall
=
function
()
{
/* Typescript will keep us from calling walkFull with a legacyAST.
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.
*/
astWalker
.
walkFull
(
<
AstNode
>
legacyNode
,
(
node
:
AstNode
)
=>
{
count
+=
1
;
});
}
t
.
throws
(
badCall
,
/first argument should be an ast/
,
"passing legacyAST fails"
);
st
.
equal
(
count
,
0
,
"traverses no AST nodes"
);
st
.
end
();
});
});
function
checkProgramDirective
(
st
:
tape
.
Test
,
node
:
AstNode
)
{
...
...
remix-astwalker/tests/resources/newAST.ts
View file @
a600b30a
This diff is collapsed.
Click to expand it.
remix-astwalker/tests/sourceMappings.ts
0 → 100644
View file @
a600b30a
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
();
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment