Skip to content

Commit 58d5402

Browse files
committed
Extracts, guards, and queries
1 parent 39deb03 commit 58d5402

38 files changed

+1687
-59
lines changed

CHANGELOG.md

+28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
# Changelog
22

3+
## 0.2.0
4+
5+
- Add Traverser (with support for `break/stop` and `skip/shallow`)
6+
- Add guards:
7+
- `isAssignmentPattern`
8+
- `isBinaryExpression`
9+
- `isCallExpression`
10+
- `isIdentifier`
11+
- `isLiteral`
12+
- `isLogicalExpression`
13+
- `isMemberExpression`
14+
- `isReturnBlockStatement`
15+
- `isTemplateLiteral`
16+
- `isUnaryExpression`
17+
- `isVariableDeclarationOfKind`
18+
- Add queries:
19+
- `findAll`
20+
- `findFirstOfType`
21+
- `findFirst`
22+
- `findLiteral`
23+
- `findMemberCall`
24+
- `findNewExpression`
25+
- `findRawLiteral`
26+
- `findTopLevelConstant`
27+
- Add extracts:
28+
- `extractExports`
29+
- `extractVariables`
30+
331
## 0.1.0
432

533
Initial release

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@exercism/static-analysis",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Exercism static analysis library for javascript and typescript",
55
"repository": "https://github.com/SleeplessByte/exercism-static-analysis",
66
"author": "Derk-Jan Karrenbeld <[email protected]>",
@@ -10,7 +10,7 @@
1010
"build": "yarn build:code && yarn build:types",
1111
"build:code": "yarn babel src --out-dir dist --extensions .ts",
1212
"build:types": "tsc --declaration --emitDeclarationOnly --outDir dist",
13-
"prepublish": "yarn test",
13+
"prepublish": "yarn test && yarn build",
1414
"lint": "yarn eslint . --ext ts,js,tsx,jsx,mjs",
1515
"test": "jest"
1616
},

src/AstParser.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,36 @@ export class ParsedSource {
7171
) {}
7272
}
7373

74+
const DEFAULT_PARSE_OPTIONS: ParseOptions = {
75+
comment: false,
76+
loc: false,
77+
}
78+
7479
export class AstParser {
80+
/**
81+
* Holds a parser that is recommended for representing. This means that it
82+
* does not hold locational information (where differences are caused by
83+
* whitespace differences) or commentary.
84+
*/
85+
public static REPRESENTER: AstParser = new AstParser({
86+
comment: false,
87+
loc: false,
88+
range: false,
89+
})
90+
91+
/**
92+
* Holds a parser that is recommended for analysis. This means that it dóés
93+
* hold locational information (in order to be able to extract tokens), but
94+
* does not hold any commentary.
95+
*/
96+
public static ANALYZER: AstParser = new AstParser({
97+
comment: false,
98+
loc: true,
99+
range: true,
100+
})
101+
75102
constructor(
76-
private readonly options?: ParseOptions,
103+
private readonly options: ParseOptions = DEFAULT_PARSE_OPTIONS,
77104
private readonly n = 1
78105
) {}
79106

src/AstTraverser.ts

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { visitorKeys } from '@typescript-eslint/visitor-keys'
2+
import { TSESTree } from '@typescript-eslint/typescript-estree'
3+
4+
type Node = TSESTree.Node
5+
6+
function isValidNode(x: unknown): x is Node {
7+
return (
8+
typeof x === 'object' &&
9+
x !== null &&
10+
Object.prototype.hasOwnProperty.call(x, 'type') &&
11+
typeof (x as { type: unknown }).type === 'string'
12+
)
13+
}
14+
15+
function getVisitorKeysForNode(
16+
allVisitorKeys: typeof visitorKeys,
17+
node: Node
18+
): readonly (keyof Node)[] {
19+
const keys = allVisitorKeys[node.type]
20+
return (keys ?? []) as never
21+
}
22+
23+
type TraverseOptions = {
24+
[key: string]: (
25+
this: AstTraverser,
26+
node: Node,
27+
parent: Node | undefined
28+
) => void
29+
} & Partial<{
30+
enter: (this: AstTraverser, node: Node, parent: Node | undefined) => void
31+
exit: (this: AstTraverser, node: Node, parent: Node | undefined) => void
32+
}>
33+
34+
export class AstTraverser {
35+
private readonly allVisitorKeys = visitorKeys
36+
private readonly selectors: TraverseOptions
37+
private readonly setParentPointers: boolean
38+
private stopped: boolean
39+
private skipped: boolean
40+
41+
constructor(selectors: TraverseOptions, setParentPointers = false) {
42+
this.selectors = selectors
43+
this.setParentPointers = setParentPointers
44+
this.stopped = false
45+
this.skipped = false
46+
}
47+
48+
public break(): void {
49+
this.stopped = true
50+
}
51+
52+
public skip(): void {
53+
this.skipped = true
54+
}
55+
56+
public traverse(
57+
node: unknown,
58+
parent?: TSESTree.Node | undefined,
59+
skipChildren = false
60+
): void {
61+
if (!isValidNode(node)) {
62+
return
63+
}
64+
65+
if (parent === undefined) {
66+
this.stopped = false
67+
}
68+
69+
if (this.setParentPointers) {
70+
node.parent = parent
71+
}
72+
73+
const { enter, exit, [node.type]: onSelector } = this.selectors
74+
75+
this.skipped = false
76+
77+
if (enter) {
78+
enter.call(this, node, parent)
79+
}
80+
81+
if (onSelector) {
82+
onSelector.call(this, node, parent)
83+
}
84+
85+
if (this.stopped) {
86+
return
87+
}
88+
89+
if (!skipChildren && !this.skipped) {
90+
const keys = getVisitorKeysForNode(this.allVisitorKeys, node)
91+
92+
for (const key of keys) {
93+
const childOrChildren = node[key]
94+
const children = Array.isArray(childOrChildren)
95+
? childOrChildren
96+
: [childOrChildren]
97+
98+
for (const child of children) {
99+
if (this.stopped) {
100+
return
101+
}
102+
103+
this.traverse(child, node, this.skipped)
104+
}
105+
}
106+
}
107+
108+
if (this.stopped) {
109+
return
110+
}
111+
112+
if (exit) {
113+
exit.call(this, node, parent)
114+
}
115+
}
116+
}
117+
118+
export function traverse(root: Node, options: TraverseOptions): void {
119+
return new AstTraverser(options).traverse(root)
120+
}

0 commit comments

Comments
 (0)