Skip to content

Commit fb842db

Browse files
ning-yremo5000
authored andcommitted
Fix parser not checking rules/* when minified (#126)
* Fix behaviour on server, but tests fail * Fix off-by-one allowed syntax types conditional I was tired, okay * Improve readability * Remove commented-out line
1 parent b65c0a9 commit fb842db

File tree

3 files changed

+114
-90
lines changed

3 files changed

+114
-90
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
}
3030
},
3131
"dependencies": {
32+
"acorn": "^5.7.1",
3233
"astring": "^1.3.0",
3334
"common-tags": "^1.7.2",
3435
"flexboxgrid": "^6.3.1",

src/slang/parser.ts

Lines changed: 109 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as es from 'estree'
66

77
import rules from './rules'
88
import syntaxTypes from './syntaxTypes'
9-
import { Context, ErrorSeverity, ErrorType, SourceError } from './types'
9+
import { Context, ErrorSeverity, ErrorType, Rule, SourceError } from './types'
1010

1111
// tslint:disable-next-line:interface-name
1212
export interface ParserOptions {
@@ -19,15 +19,15 @@ export class DisallowedConstructError implements SourceError {
1919
public nodeType: string
2020

2121
constructor(public node: es.Node) {
22-
this.nodeType = this.splitNodeType()
22+
this.nodeType = this.formatNodeType(this.node.type)
2323
}
2424

2525
get location() {
2626
return this.node.loc!
2727
}
2828

2929
public explain() {
30-
return `${this.nodeType} is not allowed`
30+
return `${this.nodeType} are not allowed`
3131
}
3232

3333
public elaborate() {
@@ -36,20 +36,22 @@ export class DisallowedConstructError implements SourceError {
3636
`
3737
}
3838

39-
private splitNodeType() {
40-
const nodeType = this.node.type
41-
const tokens: string[] = []
42-
let soFar = ''
43-
for (let i = 0; i < nodeType.length; i++) {
44-
const isUppercase = nodeType[i] === nodeType[i].toUpperCase()
45-
if (isUppercase && i > 0) {
46-
tokens.push(soFar)
47-
soFar = ''
48-
} else {
49-
soFar += nodeType[i]
50-
}
39+
/**
40+
* Converts estree node.type into english
41+
* e.g. ThisExpression -> 'this' expressions
42+
* Property -> Properties
43+
* EmptyStatement -> Empty Statements
44+
*/
45+
private formatNodeType(nodeType: string) {
46+
switch (nodeType) {
47+
case 'ThisExpression':
48+
return "'this' expressions"
49+
case 'Property':
50+
return 'Properties'
51+
default:
52+
const words = nodeType.split(/(?=[A-Z])/)
53+
return words.map((word, i) => (i === 0 ? word : word.toLowerCase())).join(' ') + 's'
5154
}
52-
return tokens.join(' ')
5355
}
5456
}
5557

@@ -95,48 +97,31 @@ export class TrailingCommaError implements SourceError {
9597
}
9698
}
9799

98-
export const freshId = (() => {
99-
let id = 0
100-
return () => {
101-
id++
102-
return 'node_' + id
103-
}
104-
})()
105-
106-
function compose<T extends es.Node, S>(
107-
w1: (node: T, state: S) => void,
108-
w2: (node: T, state: S) => void
109-
) {
110-
return (node: T, state: S) => {
111-
w1(node, state)
112-
w2(node, state)
113-
}
114-
}
115-
116-
const walkers: {
117-
[name: string]: (node: es.Node, context: Context) => void
118-
} = {}
119-
120-
for (const type of Object.keys(syntaxTypes)) {
121-
walkers[type] = (node: es.Node, context: Context) => {
122-
const id = freshId()
123-
Object.defineProperty(node, '__id', {
124-
enumerable: true,
125-
configurable: false,
126-
writable: false,
127-
value: id
128-
})
129-
context.cfg.nodes[id] = {
130-
id,
131-
node,
132-
scope: undefined,
133-
usages: []
134-
}
135-
context.cfg.edges[id] = []
136-
if (syntaxTypes[node.type] > context.chapter) {
137-
context.errors.push(new DisallowedConstructError(node))
100+
export function parse(source: string, context: Context) {
101+
let program: es.Program | undefined
102+
try {
103+
program = acornParse(source, createAcornParserOptions(context))
104+
simple(program, walkers, undefined, context)
105+
} catch (error) {
106+
if (error instanceof SyntaxError) {
107+
// tslint:disable-next-line:no-any
108+
const loc = (error as any).loc
109+
const location = {
110+
start: { line: loc.line, column: loc.column },
111+
end: { line: loc.line, column: loc.column + 1 }
112+
}
113+
context.errors.push(new FatalSyntaxError(location, error.toString()))
114+
} else {
115+
throw error
138116
}
139117
}
118+
const hasErrors = context.errors.find(m => m.severity === ErrorSeverity.ERROR)
119+
if (program && !hasErrors) {
120+
// context.cfg.scopes[0].node = program
121+
return program
122+
} else {
123+
return undefined
124+
}
140125
}
141126

142127
const createAcornParserOptions = (context: Context): AcornOptions => ({
@@ -163,43 +148,77 @@ const createAcornParserOptions = (context: Context): AcornOptions => ({
163148
}
164149
})
165150

166-
rules.forEach(rule => {
167-
const keys = Object.keys(rule.checkers)
168-
keys.forEach(key => {
169-
walkers[key] = compose(walkers[key], (node, context) => {
170-
if (typeof rule.disableOn !== 'undefined' && context.chapter >= rule.disableOn) {
171-
return
151+
function createWalkers(
152+
allowedSyntaxes: { [nodeName: string]: number },
153+
parserRules: Array<Rule<es.Node>>
154+
) {
155+
const newWalkers = new Map<string, (n: es.Node, c: Context) => void>()
156+
157+
// Provide callbacks checking for disallowed syntaxes, such as case, switch...
158+
const syntaxPairs = Object.entries(allowedSyntaxes)
159+
syntaxPairs.map(pair => {
160+
const syntax = pair[0]
161+
const allowedChap = pair[1]
162+
newWalkers.set(syntax, (node: es.Node, context: Context) => {
163+
const id = freshId()
164+
Object.defineProperty(node, '__id', {
165+
enumerable: true,
166+
configurable: false,
167+
writable: false,
168+
value: id
169+
})
170+
context.cfg.nodes[id] = {
171+
id,
172+
node,
173+
scope: undefined,
174+
usages: []
175+
}
176+
context.cfg.edges[id] = []
177+
if (context.chapter < allowedChap) {
178+
context.errors.push(new DisallowedConstructError(node))
172179
}
173-
const checker = rule.checkers[key]
174-
const errors = checker(node)
175-
errors.forEach(e => context.errors.push(e))
176180
})
177181
})
178-
})
179182

180-
export const parse = (source: string, context: Context) => {
181-
let program: es.Program | undefined
182-
try {
183-
program = acornParse(source, createAcornParserOptions(context))
184-
simple(program, walkers, undefined, context)
185-
} catch (error) {
186-
if (error instanceof SyntaxError) {
187-
// tslint:disable-next-line:no-any
188-
const loc = (error as any).loc
189-
const location = {
190-
start: { line: loc.line, column: loc.column },
191-
end: { line: loc.line, column: loc.column + 1 }
183+
// Provide callbacks checking for rule violations, e.g. no block arrow funcs, non-empty lists...
184+
parserRules.forEach(rule => {
185+
const checkers = rule.checkers
186+
const syntaxCheckerPair = Object.entries(checkers)
187+
syntaxCheckerPair.forEach(pair => {
188+
const syntax = pair[0]
189+
const checker = pair[1]
190+
const oldCheck = newWalkers.get(syntax)
191+
const newCheck = (node: es.Node, context: Context) => {
192+
if (typeof rule.disableOn !== 'undefined' && context.chapter >= rule.disableOn) {
193+
return
194+
}
195+
const errors = checker(node)
196+
errors.forEach(e => context.errors.push(e))
192197
}
193-
context.errors.push(new FatalSyntaxError(location, error.toString()))
194-
} else {
195-
throw error
196-
}
197-
}
198-
const hasErrors = context.errors.find(m => m.severity === ErrorSeverity.ERROR)
199-
if (program && !hasErrors) {
200-
// context.cfg.scopes[0].node = program
201-
return program
202-
} else {
203-
return undefined
204-
}
198+
newWalkers.set(syntax, (node, context) => {
199+
if (oldCheck) {
200+
oldCheck(node, context)
201+
}
202+
newCheck(node, context)
203+
})
204+
})
205+
})
206+
207+
return mapToObj(newWalkers)
205208
}
209+
210+
export const freshId = (() => {
211+
let id = 0
212+
return () => {
213+
id++
214+
return 'node_' + id
215+
}
216+
})()
217+
218+
const mapToObj = (map: Map<string, any>) =>
219+
Array.from(map).reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {})
220+
221+
const walkers: { [name: string]: (node: es.Node, ctxt: Context) => void } = createWalkers(
222+
syntaxTypes,
223+
rules
224+
)

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,10 @@ acorn@^5.0.0, acorn@^5.3.0:
369369
version "5.5.3"
370370
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
371371

372+
acorn@^5.7.1:
373+
version "5.7.1"
374+
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
375+
372376
[email protected], address@^1.0.1:
373377
version "1.0.3"
374378
resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"

0 commit comments

Comments
 (0)