@@ -6,7 +6,7 @@ import * as es from 'estree'
66
77import rules from './rules'
88import 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
1212export 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
142127const 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+ )
0 commit comments