diff --git a/grammars/sql.cson b/grammars/sql.cson index 3624c2f..96d4477 100644 --- a/grammars/sql.cson +++ b/grammars/sql.cson @@ -87,49 +87,53 @@ 'name': 'meta.alter.sql' } { + 'match': '(?i)\\b(bigserial|boolean|box|bytea|cidr|circle|date|datetime|datetime2|double\\s+precision|enum|inet|integer|interval|line|lseg|macaddr|money|oid|path|point|polygon|real|serial|sysdate|text|uniqueidentifier)\\b' + 'name': 'storage.type.sql' + } + { + 'match': '(?i)\\b(bigint|bit(?:\\s+varying)?|n?char|character(?:\\s+varying)?|float|int|number|smallint|time(?:stamp)?tz|tinyint|n?varchar\\d?)\\b(?:\\s*(\\()\\s*(\\d*)\\s*(\\)))?' 'captures': '1': 'name': 'storage.type.sql' '2': - 'name': 'storage.type.sql' + 'name': 'punctuation.definition.parameters.bracket.round.begin.sql' '3': 'name': 'constant.numeric.sql' '4': + 'name': 'punctuation.definition.parameters.bracket.round.end.sql' + } + { + 'match': '(?i)\\b(numeric|decimal)\\b(?:\\s*(\\()\\s*(\\d*)(?:\\s*(,)\\s*(\\d*))?\\s*(\\)))?' + 'captures': + '1': 'name': 'storage.type.sql' + '2': + 'name': 'punctuation.definition.parameters.bracket.round.begin.sql' + '3': + 'name': 'constant.numeric.sql' + '4': + 'name': 'punctuation.separator.parameters.comma.sql' '5': 'name': 'constant.numeric.sql' '6': + 'name': 'punctuation.definition.parameters.bracket.round.end.sql' + } + { + 'match': '(?i)\\b(time(?:stamp)?)\\b(?:\\s*(\\()\\s*(\\d*)\\s*(\\)))?(?:\\s*(with(?:out)?\\s+time\\s+zone)\\b)?' + 'captures': + '1': 'name': 'storage.type.sql' - '7': - 'name': 'constant.numeric.sql' - '8': - 'name': 'constant.numeric.sql' - '9': - 'name': 'storage.type.sql' - '10': + '2': + 'name': 'punctuation.definition.parameters.bracket.round.begin.sql' + '3': 'name': 'constant.numeric.sql' - '11': + '4': + 'name': 'punctuation.definition.parameters.bracket.round.end.sql' + '5': 'name': 'storage.type.sql' - 'match': ''' - (?xi) - # normal stuff, capture 1 - \\b(bigint|bigserial|bit|boolean|box|bytea|cidr|circle|date|datetime|datetime2|double\\sprecision|enum|inet|int|integer|line|lseg|macaddr|money|oid|path|point|polygon|real|float|serial|smallint|tinyint|sysdate|text|uniqueidentifier)\\b - - # numeric suffix, capture 2 + 3i - |\\b(bit\\svarying|character\\s(?:varying)?|tinyint|var\\schar|float|interval)\\((\\d+)\\) - - # optional numeric suffix, capture 4 + 5i - |\\b(char|number|n?varchar\\d?|time(?:stamp)?tz)\\b(?:\\s*\\(\\s*(\\d+)\\s*\\))? - - # special case, capture 6 + 7i + 8i - |\\b(numeric|decimal)\\b(?:\\((\\d+),(\\d+)\\))? - - # special case, captures 9, 10i, 11 - |\\b(time(?:stamp)?)\\b(?:\\s*\\(\\s*(\\d+)\\s*\\))?(?:\\s*(with(?:out)?\\s+time\\s+zone\\b))? - ''' - } - { - 'match': '(?i:\\b((?:primary|foreign)\\s+key|references|on\\sdelete(\\s+cascade)?|check|constraint|unique|default)\\b)' + } + { + 'match': '(?i:\\b((?:primary|foreign)\\s+key|references|on\\s+delete(\\s+cascade)?|check|constraint|unique|default)\\b)' 'name': 'storage.modifier.sql' } { @@ -227,8 +231,10 @@ '1': 'name': 'constant.other.database-name.sql' '2': + 'name': 'punctuation.separator.period.sql' + '3': 'name': 'constant.other.table-name.sql' - 'match': '(\\w+?)\\.(\\w+)' + 'match': '(\\w+?)(\\.)(\\w+)' } { 'include': '#strings' @@ -236,6 +242,9 @@ { 'include': '#regexps' } + { + 'include': '#punctuation' + } ] 'repository': 'comments': @@ -260,6 +269,40 @@ 'name': 'comment.block.sql' } ] + 'punctuation': + 'patterns': [ + { + 'begin': '\\(' + 'end': '\\)' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.section.bracket.round.begin.sql' + 'endCaptures': + '0': + 'name': 'punctuation.definition.section.bracket.round.end.sql' + 'patterns': [ + { + 'include': '$self' + } + ] + } + { + 'match': '\\)' + 'name': 'punctuation.unmatched.bracket.round.end.sql' + } + { + 'match': ',' + 'name': 'punctuation.separator.comma.sql' + } + { + 'match': '\\.' + 'name': 'punctuation.separator.period.sql' + } + { + 'match': ';' + 'name': 'punctuation.terminator.statement.semicolon.sql' + } + ] 'regexps': 'patterns': [ { diff --git a/spec/grammar-spec.coffee b/spec/grammar-spec.coffee index 2adc154..5d8bc62 100644 --- a/spec/grammar-spec.coffee +++ b/spec/grammar-spec.coffee @@ -113,37 +113,63 @@ describe "SQL grammar", -> expect(tokens[1]).toEqual value: 'Test', scopes: ['source.sql', 'string.quoted.double.sql'] expect(tokens[2]).toEqual value: '"', scopes: ['source.sql', 'string.quoted.double.sql', 'punctuation.definition.string.end.sql'] - it 'tokenizes the time type', -> - {tokens} = grammar.tokenizeLine('TIME') - expect(tokens[0]).toEqual value: 'TIME', scopes: ['source.sql', 'storage.type.sql'] - - {tokens} = grammar.tokenizeLine('TIME WITH TIME ZONE') - expect(tokens[0]).toEqual value: 'TIME', scopes: ['source.sql', 'storage.type.sql'] - expect(tokens[2]).toEqual value: 'WITH TIME ZONE', scopes: ['source.sql', 'storage.type.sql'] - - {tokens} = grammar.tokenizeLine('TIME(1)WITHOUT TIME ZONE\'23:00\'') - expect(tokens[0]).toEqual value: 'TIME', scopes: ['source.sql', 'storage.type.sql'] - expect(tokens[2]).toEqual value: '1', scopes: ['source.sql', 'constant.numeric.sql'] - expect(tokens[4]).toEqual value: 'WITHOUT TIME ZONE', scopes: ['source.sql', 'storage.type.sql'] - - it 'tokenizes the timestamp type', -> - {tokens} = grammar.tokenizeLine('TIMESTAMP ( 12 ) WITH TIME ZONE') - expect(tokens[0]).toEqual value: 'TIMESTAMP', scopes: ['source.sql', 'storage.type.sql'] - expect(tokens[2]).toEqual value: '12', scopes: ['source.sql', 'constant.numeric.sql'] - expect(tokens[4]).toEqual value: 'WITH TIME ZONE', scopes: ['source.sql', 'storage.type.sql'] - - it 'tokenizes the timestamptz type', -> - {tokens} = grammar.tokenizeLine('timestamptz') - expect(tokens[0]).toEqual value: 'timestamptz', scopes: ['source.sql', 'storage.type.sql'] - - {tokens} = grammar.tokenizeLine('TIMESTAMPTZ(2)NOT NULL') - expect(tokens[0]).toEqual value: 'TIMESTAMPTZ', scopes: ['source.sql', 'storage.type.sql'] - expect(tokens[2]).toEqual value: '2', scopes: ['source.sql', 'constant.numeric.sql'] - - it 'tokenizes the timetz type', -> - {tokens} = grammar.tokenizeLine('timetz (2)') - expect(tokens[0]).toEqual value: 'timetz', scopes: ['source.sql', 'storage.type.sql'] - expect(tokens[2]).toEqual value: '2', scopes: ['source.sql', 'constant.numeric.sql'] + it 'tokenizes storage types', -> + lines = grammar.tokenizeLines(''' + datetime + double precision + integer + ''') + expect(lines[0][0]).toEqual value: 'datetime', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[1][0]).toEqual value: 'double precision', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[2][0]).toEqual value: 'integer', scopes: ['source.sql', 'storage.type.sql'] + + it 'tokenizes storage types with an optional argument', -> + lines = grammar.tokenizeLines(''' + bit varying + int() + timestamptz(1) + ''') + expect(lines[0][0]).toEqual value: 'bit varying', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[1][0]).toEqual value: 'int', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[1][1]).toEqual value: '(', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.begin.sql'] + expect(lines[1][2]).toEqual value: ')', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.end.sql'] + expect(lines[2][0]).toEqual value: 'timestamptz', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[2][1]).toEqual value: '(', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.begin.sql'] + expect(lines[2][2]).toEqual value: '1', scopes: ['source.sql', 'constant.numeric.sql'] + expect(lines[2][3]).toEqual value: ')', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.end.sql'] + + it 'tokenizes storage types with two optional arguments', -> + lines = grammar.tokenizeLines(''' + decimal + decimal(1) + numeric(1,1) + ''') + expect(lines[0][0]).toEqual value: 'decimal', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[1][0]).toEqual value: 'decimal', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[1][1]).toEqual value: '(', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.begin.sql'] + expect(lines[1][2]).toEqual value: '1', scopes: ['source.sql', 'constant.numeric.sql'] + expect(lines[1][3]).toEqual value: ')', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.end.sql'] + expect(lines[2][0]).toEqual value: 'numeric', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[2][1]).toEqual value: '(', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.begin.sql'] + expect(lines[2][2]).toEqual value: '1', scopes: ['source.sql', 'constant.numeric.sql'] + expect(lines[2][3]).toEqual value: ',', scopes: ['source.sql', 'punctuation.separator.parameters.comma.sql'] + expect(lines[2][4]).toEqual value: '1', scopes: ['source.sql', 'constant.numeric.sql'] + expect(lines[2][5]).toEqual value: ')', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.end.sql'] + + it 'tokenizes storage types with time zones', -> + lines = grammar.tokenizeLines(''' + time + time(1) with time zone + timestamp without time zone + ''') + expect(lines[0][0]).toEqual value: 'time', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[1][0]).toEqual value: 'time', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[1][1]).toEqual value: '(', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.begin.sql'] + expect(lines[1][2]).toEqual value: '1', scopes: ['source.sql', 'constant.numeric.sql'] + expect(lines[1][3]).toEqual value: ')', scopes: ['source.sql', 'punctuation.definition.parameters.bracket.round.end.sql'] + expect(lines[1][5]).toEqual value: 'with time zone', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[2][0]).toEqual value: 'timestamp', scopes: ['source.sql', 'storage.type.sql'] + expect(lines[2][2]).toEqual value: 'without time zone', scopes: ['source.sql', 'storage.type.sql'] it 'tokenizes comments', -> {tokens} = grammar.tokenizeLine('-- comment') @@ -167,3 +193,40 @@ describe "SQL grammar", -> expect(tokens[3]).toEqual value: ' WITH ', scopes: ['source.sql', 'comment.block.sql'] expect(tokens[4]).toEqual value: '*/', scopes: ['source.sql', 'comment.block.sql', 'punctuation.definition.comment.sql'] expect(tokens[6]).toEqual value: 'AND', scopes: ['source.sql', 'keyword.other.DML.sql'] + + describe 'punctuation', -> + it 'tokenizes parentheses', -> + {tokens} = grammar.tokenizeLine('WHERE salary > (SELECT avg(salary) FROM employees)') + expect(tokens[0]).toEqual value: 'WHERE', scopes: ['source.sql', 'keyword.other.DML.sql'] + expect(tokens[1]).toEqual value: ' salary ', scopes: ['source.sql'] + expect(tokens[2]).toEqual value: '>', scopes: ['source.sql', 'keyword.operator.comparison.sql'] + expect(tokens[4]).toEqual value: '(', scopes: ['source.sql', 'punctuation.definition.section.bracket.round.begin.sql'] + expect(tokens[5]).toEqual value: 'SELECT', scopes: ['source.sql', 'keyword.other.DML.sql'] + expect(tokens[7]).toEqual value: 'avg', scopes: ['source.sql', 'support.function.aggregate.sql'] + expect(tokens[8]).toEqual value: '(', scopes: ['source.sql', 'punctuation.definition.section.bracket.round.begin.sql'] + expect(tokens[9]).toEqual value: 'salary', scopes: ['source.sql'] + expect(tokens[10]).toEqual value: ')', scopes: ['source.sql', 'punctuation.definition.section.bracket.round.end.sql'] + expect(tokens[12]).toEqual value: 'FROM', scopes: ['source.sql', 'keyword.other.DML.sql'] + expect(tokens[13]).toEqual value: ' employees', scopes: ['source.sql'] + expect(tokens[14]).toEqual value: ')', scopes: ['source.sql', 'punctuation.definition.section.bracket.round.end.sql'] + + it 'tokenizes commas', -> + {tokens} = grammar.tokenizeLine('name, year') + expect(tokens[0]).toEqual value: 'name', scopes: ['source.sql'] + expect(tokens[1]).toEqual value: ',', scopes: ['source.sql', 'punctuation.separator.comma.sql'] + expect(tokens[2]).toEqual value: ' year', scopes: ['source.sql'] + + it 'tokenizes periods', -> + {tokens} = grammar.tokenizeLine('.') + expect(tokens[0]).toEqual value: '.', scopes: ['source.sql', 'punctuation.separator.period.sql'] + + {tokens} = grammar.tokenizeLine('database.table') + expect(tokens[0]).toEqual value: 'database', scopes: ['source.sql', 'constant.other.database-name.sql'] + expect(tokens[1]).toEqual value: '.', scopes: ['source.sql', 'punctuation.separator.period.sql'] + expect(tokens[2]).toEqual value: 'table', scopes: ['source.sql', 'constant.other.table-name.sql'] + + it 'tokenizes semicolons', -> + {tokens} = grammar.tokenizeLine('ORDER BY year;') + expect(tokens[0]).toEqual value: 'ORDER BY', scopes: ['source.sql', 'keyword.other.DML.sql'] + expect(tokens[1]).toEqual value: ' year', scopes: ['source.sql'] + expect(tokens[2]).toEqual value: ';', scopes: ['source.sql', 'punctuation.terminator.statement.semicolon.sql']