Skip to content

Commit

Permalink
feat(state): Safely added ability to disable semicolons
Browse files Browse the repository at this point in the history
  • Loading branch information
Mouradvc committed Jul 8, 2019
1 parent 68bc7ba commit 552a61d
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 13 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ The `options` are:
- `output`: output stream to write the rendered code to (defaults to `null`)
- `generator`: custom code generator (defaults to `astring.baseGenerator`)
- `sourceMap`: [source map generator](https://github.com/mozilla/source-map#sourcemapgenerator) (defaults to `null`)
- `semicolon`: boolean indicating wether the generator should add semicolons after each statement or not (defaults to `true`)

### `baseGenerator: object`

Expand Down
46 changes: 33 additions & 13 deletions src/astring.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ export const baseGenerator = {
}),
ClassBody: BlockStatement,
EmptyStatement(node, state) {
state.write(';')
state.terminate()
},
ExpressionStatement(node, state) {
const precedence = EXPRESSIONS_PRECEDENCE[node.expression.type]
Expand All @@ -307,7 +307,7 @@ export const baseGenerator = {
} else {
this[node.expression.type](node.expression, state)
}
state.write(';')
state.terminate()
},
IfStatement(node, state) {
state.write('if (')
Expand All @@ -330,15 +330,15 @@ export const baseGenerator = {
state.write(' ')
this[node.label.type](node.label, state)
}
state.write(';')
state.terminate()
},
ContinueStatement(node, state) {
state.write('continue')
if (node.label != null) {
state.write(' ')
this[node.label.type](node.label, state)
}
state.write(';')
state.terminate()
},
WithStatement(node, state) {
state.write('with (')
Expand Down Expand Up @@ -390,12 +390,12 @@ export const baseGenerator = {
state.write(' ')
this[node.argument.type](node.argument, state)
}
state.write(';')
state.terminate()
},
ThrowStatement(node, state) {
state.write('throw ')
this[node.argument.type](node.argument, state)
state.write(';')
state.terminate()
},
TryStatement(node, state) {
state.write('try ')
Expand Down Expand Up @@ -427,7 +427,8 @@ export const baseGenerator = {
this[node.body.type](node.body, state)
state.write(' while (')
this[node.test.type](node.test, state)
state.write(');')
state.write(')')
state.terminate()
},
ForStatement(node, state) {
state.write('for (')
Expand Down Expand Up @@ -466,7 +467,9 @@ export const baseGenerator = {
}),
ForOfStatement: ForInStatement,
DebuggerStatement(node, state) {
state.write('debugger;' + state.lineEnd)
state.write('debugger')
state.terminate()
state.write(state.lineEnd)
},
FunctionDeclaration: (FunctionDeclaration = function(node, state) {
state.write(
Expand All @@ -482,7 +485,7 @@ export const baseGenerator = {
FunctionExpression: FunctionDeclaration,
VariableDeclaration(node, state) {
formatVariableDeclaration(state, node)
state.write(';')
state.terminate()
},
VariableDeclarator(node, state) {
this[node.id.type](node.id, state)
Expand Down Expand Up @@ -547,7 +550,7 @@ export const baseGenerator = {
state.write(' from ')
}
this.Literal(node.source, state)
state.write(';')
state.terminate()
},
ExportDefaultDeclaration(node, state) {
state.write('export default ')
Expand All @@ -557,7 +560,7 @@ export const baseGenerator = {
node.declaration.type[0] !== 'F'
) {
// All expression nodes except `FunctionExpression`
state.write(';')
state.terminate()
}
},
ExportNamedDeclaration(node, state) {
Expand Down Expand Up @@ -588,13 +591,13 @@ export const baseGenerator = {
state.write(' from ')
this.Literal(node.source, state)
}
state.write(';')
state.terminate()
}
},
ExportAllDeclaration(node, state) {
state.write('export * from ')
this.Literal(node.source, state)
state.write(';')
state.terminate()
},
MethodDefinition(node, state) {
if (node.static) {
Expand Down Expand Up @@ -977,9 +980,16 @@ class State {
source: setup.sourceMap.file || setup.sourceMap._file,
}
}
// Semi-colon behaviour
this.semicolon = setup.semicolon != null ? setup.semicolon : true
}

write(code) {
const sensitiveChars = '[(-'
if (sensitiveChars.indexOf(code.charAt(0)) !== -1 && this.careful) {
this.output += ';'
}
this.careful = false
this.output += code
}

Expand Down Expand Up @@ -1031,6 +1041,14 @@ class State {
toString() {
return this.output
}

terminate() {
if (this.semicolon) {
this.write(';')
} else {
this.careful = true
}
}
}

export function generate(node, options) {
Expand All @@ -1044,6 +1062,8 @@ export function generate(node, options) {
- `comments`: generate comments if `true` (defaults to `false`)
- `output`: output stream to write the rendered code to (defaults to `null`)
- `generator`: custom code generator (defaults to `baseGenerator`)
- `sourceMap`: [source map generator](https://github.com/mozilla/source-map#sourcemapgenerator) (defaults to `null`)
- `semicolon`: boolean indicating wether the generator should add semicolons after each statement or not (defaults to `true`)
*/
const state = new State(options)
// Travel through the AST node and generate the code
Expand Down
23 changes: 23 additions & 0 deletions src/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ test('Syntax check', assert => {
})
})

test('Syntax check without semicolon', assert => {
const dirname = path.join(FIXTURES_FOLDER, 'syntax')
const files = fs.readdirSync(dirname).sort()
const options = {
ecmaVersion,
sourceType: 'module',
}
files.forEach(filename => {
const code = normalizeNewline(
fs.readFileSync(path.join(dirname, filename), 'utf8'),
)
const ast = parse(code, options)
const backToCode = generate(ast, {
semicolon: false,
})
assert.is(
backToCode,
code.replace(/;$/gm, ''),
filename.substring(0, filename.length - 3),
)
})
})

test('Tree comparison', assert => {
const dirname = path.join(FIXTURES_FOLDER, 'tree')
const files = fs.readdirSync(dirname).sort()
Expand Down

0 comments on commit 552a61d

Please sign in to comment.