Skip to content

Commit

Permalink
Validate query logs syntax errors to the console #56
Browse files Browse the repository at this point in the history
Added an error listener to the lexer to ensure that all error message are handled through the same error handler
and do not print out to the console

resolves #56
  • Loading branch information
paustint committed Feb 8, 2019
1 parent 0d04ab1 commit d603821
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 11 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.0.1
- Ensured that nothing is logged directly to the console unless logging is enabled

## 1.0
## 1.0.0
### Changed
**!BREAKING CHANGES!**
- Added literal type information to fields to provide additional information about the field type. (#51)
Expand Down
17 changes: 9 additions & 8 deletions debug/test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
var soqlParserJs = require('./lib');

const query = `
SELECT Id, Name, FORMAT(Amount),
(SELECT Quantity, ListPrice, PricebookEntry.UnitPrice, PricebookEntry.Name FROM OpportunityLineItems)
FROM Opportunity
WHERE CreatedDate > LAST_N_YEARS:1
AND StageName = 'Closed Won'
LIMIT 150
`;
const query = `@`;
// const query = `
// SELECT Id, Name, FORMAT(Amount),
// (SELECT Quantity, ListPrice, PricebookEntry.UnitPrice, PricebookEntry.Name FROM OpportunityLineItems)
// FROM Opportunity
// WHERE CreatedDate > LAST_N_YEARS:1
// AND StageName = 'Closed Won'
// LIMIT 150
// `;
// SELECT Id FROM Account WHERE (Id IN ('1', '2', '3') OR (NOT Id = '2') OR (Name LIKE '%FOO%' OR (Name LIKE '%ARM%' AND FOO = 'bar')))
// SELECT Id FROM Account WHERE dateField != '2018-10-03' AND dateField < LAST_N_DAYS:5 AND dateField < LAST_WEEK AND isDeleted = false AND someOTherField = 'someVal'
// SELECT Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Account.Name, (SELECT Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Id, Name, Foo, Bar, Baz, Bax, aaa, bbb, ccc, ddd, Contact.LastName FROM Account.Contacts), baz, (SELECT Id FROM account WHERE Boo.baz = 'bar'), bax, bar FROM Account
Expand Down
51 changes: 51 additions & 0 deletions docs/src/components/parse-soql-code-output.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import SyntaxHighlighter from 'react-syntax-highlighter/prism';
import * as CopyToClipboard from 'react-copy-to-clipboard';
import { xonokai } from 'react-syntax-highlighter/styles/prism';

import * as React from 'react';
import { Button } from 'office-ui-fabric-react/lib/Button';

export interface CodeOutputProps {
title: string | JSX.Element;
lang: string;
data?: string;
showCopyToClipboard: boolean;
copyToClipboardDisabled: boolean;
showChildrenAboveClipboard?: boolean;
customStyle?: any;
}

export default class CodeOutput extends React.Component<CodeOutputProps, any> {
constructor(props: CodeOutputProps) {
super(props);
}

public render() {
return (
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-sm12">
<div className="ms-font-l">{this.props.title}</div>
<SyntaxHighlighter language={this.props.lang} style={xonokai} customStyle={this.props.customStyle}>
{this.props.data}
</SyntaxHighlighter>
<div>
{this.props.showChildrenAboveClipboard && this.props.children}
{this.props.showCopyToClipboard && this.props.data && (
<CopyToClipboard text={this.props.data}>
<Button
primary={true}
disabled={this.props.copyToClipboardDisabled}
iconProps={{ iconName: 'copy' }}
title="Copy"
ariaLabel="Copy"
text="Copy to Clipboard"
/>
</CopyToClipboard>
)}
{!this.props.showChildrenAboveClipboard && this.props.children}
</div>
</div>
</div>
);
}
}
104 changes: 104 additions & 0 deletions docs/src/components/parse-soql-format.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as React from 'react';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { FormatOptions } from 'soql-parser-js';

export interface ParseSoqlFormatProps {
style?: any;
formatOptions: FormatOptions;
enabled: boolean;
toggleFormat: (enabled: boolean) => void;
onChange: (formatOptions: FormatOptions) => void;
}

export default class ParseSoqlFormat extends React.Component<ParseSoqlFormatProps, any> {
public setMaxFieldLen = (ev: React.SyntheticEvent<HTMLInputElement>) => {
const formatOptions = {
...this.props.formatOptions,
fieldMaxLineLen: Math.max(0, Number((ev.target as HTMLInputElement).value)),
};
this.props.onChange(formatOptions);
};

public toggleSubqueryParens = () => {
const formatOptions = {
...this.props.formatOptions,
fieldSubqueryParensOnOwnLine: !this.props.formatOptions.fieldSubqueryParensOnOwnLine,
};
this.props.onChange(formatOptions);
};

public toggleWhereClauseIndent = () => {
const formatOptions = {
...this.props.formatOptions,
whereClauseOperatorsIndented: !this.props.formatOptions.whereClauseOperatorsIndented,
};
this.props.onChange(formatOptions);
};

public render() {
return (
<div style={this.props.style}>
<Checkbox
label="Format Output"
checked={this.props.enabled}
onChange={() => this.props.toggleFormat(!this.props.enabled)}
/>
<div style={{ margin: 5, paddingLeft: 10 }}>
<div style={{ maxWidth: 400 }}>
<TextField
label={
(
<span>
Number of characters before fields wrap -{' '}
<small>
<code>fieldMaxLineLen</code>
</small>
</span>
) as any
}
type="number"
value={String(this.props.formatOptions.fieldMaxLineLen)}
onChange={this.setMaxFieldLen}
disabled={!this.props.enabled}
/>
</div>
<div style={{ marginTop: 5 }}>
<Checkbox
label={
(
<span>
Subquery Parenthesis on own line -{' '}
<small>
<code>fieldSubqueryParensOnOwnLine</code>
</small>
</span>
) as any
}
checked={this.props.formatOptions.fieldSubqueryParensOnOwnLine}
onChange={this.toggleSubqueryParens}
disabled={!this.props.enabled}
/>
</div>
<div style={{ marginTop: 5 }}>
<Checkbox
label={
(
<span>
Indent items in WHERE clause -{' '}
<small>
<code>whereClauseOperatorsIndented</code>
</small>
</span>
) as any
}
checked={this.props.formatOptions.whereClauseOperatorsIndented}
onChange={this.toggleWhereClauseIndent}
disabled={!this.props.enabled}
/>
</div>
</div>
</div>
);
}
}
8 changes: 7 additions & 1 deletion lib/SoqlParser.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { ANTLRInputStream, CommonTokenStream } from 'antlr4ts';
import { ANTLRInputStream, CommonTokenStream, TokenStream } from 'antlr4ts';
import { ParseTreeWalker } from 'antlr4ts/tree';
import * as utils from './utils';
import { SyntaxErrorListener } from './ErrorListener';
import { SOQLLexer } from './generated/SOQLLexer';
import { SOQLParser, Soql_queryContext } from './generated/SOQLParser';
import { Query } from './models/SoqlQuery.model';
import { Listener, ListenerQuick } from './SoqlListener';
import { Override } from 'antlr4ts/Decorators';

export interface ConfigBase {
logging?: boolean; // default=false
Expand All @@ -32,7 +33,12 @@ function configureDefaults(config: Partial<SoqlQueryConfig> = {}) {
*/
function getSoqlQueryContext(soql: string, config: Partial<SoqlQueryConfig> = {}): SOQLParser {
let inputStream = new ANTLRInputStream(soql);

let lexer = new SOQLLexer(inputStream);
// bug-56 - The lever must have error listeners added to ensure no logging directly to the console
lexer.removeErrorListeners();
lexer.addErrorListener(new SyntaxErrorListener());

let tokenStream = new CommonTokenStream(lexer);
const parser = new SOQLParser(tokenStream);

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "soql-parser-js",
"version": "1.0.0",
"version": "1.0.1",
"description": "Salesforce.com SOQL parser.",
"main": "dist/index.js",
"module": "dist/index.es.js",
Expand Down

0 comments on commit d603821

Please sign in to comment.