diff --git a/.travis.yml b/.travis.yml index 75e1422f7a1..97ec1a205c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ notifications: language: node_js node_js: - - "0.12" + - "4.1.1" install: - npm install diff --git a/gulpfile.js b/gulpfile.js index 871465194a6..913dc9dee33 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -10,8 +10,8 @@ var paths = { tests_js: [ // test with dependencies on 'vscode' do not run "out/test/extension.test.js", - "out/test/lexer.test.js", - "out/test/scanner.test.js" + "out/test/cmd_line/lexer.test.js", + "out/test/cmd_line/scanner.test.js", ] }; diff --git a/src/cmd_line/commands/quit.ts b/src/cmd_line/commands/quit.ts index e0f91f32ef5..1b562a79864 100644 --- a/src/cmd_line/commands/quit.ts +++ b/src/cmd_line/commands/quit.ts @@ -27,10 +27,10 @@ export class QuitCommand extends node.CommandBase { } execute() : void { - this.quit(this.activeTextEditor); + this.quit(); } - private quit(textEditor : vscode.TextEditor) { + private quit() { // See https://github.com/Microsoft/vscode/issues/723 if ((this.activeTextEditor.document.isDirty || this.activeTextEditor.document.isUntitled) && !this.arguments.bang) { diff --git a/src/cmd_line/lexer.ts b/src/cmd_line/lexer.ts index fbb9fdcc91f..90c688012d6 100644 --- a/src/cmd_line/lexer.ts +++ b/src/cmd_line/lexer.ts @@ -115,7 +115,6 @@ module LexerFunctions { } else { state.backup(); tokens.push(emitToken(TokenType.CommandName, state)); - state.skipWhiteSpace(); while (!state.isAtEof) { state.next(); } diff --git a/src/cmd_line/parser.ts b/src/cmd_line/parser.ts index f741c9fc813..343f9915419 100644 --- a/src/cmd_line/parser.ts +++ b/src/cmd_line/parser.ts @@ -78,7 +78,7 @@ class ParserState { this.lex(input); } - lex(input : string) { + private lex(input : string) { this.tokens = lexer.lex(input); } diff --git a/src/cmd_line/scanner.ts b/src/cmd_line/scanner.ts index 49bd024d489..c4f3a208f7a 100644 --- a/src/cmd_line/scanner.ts +++ b/src/cmd_line/scanner.ts @@ -78,14 +78,16 @@ export class Scanner { if (this.isAtEof) { return; } - while (true) { + while (!this.isAtEof) { var c = this.next(); if (c === " " || c === "\t") { continue; } break; } - this.backup(); + if (c !== Scanner.EOF && c !== ' ' && c !== '\t') { + this.backup(); + } this.ignore(); } @@ -99,6 +101,9 @@ export class Scanner { expectOneOf(...values : string[]) : void { let match = values.filter(s => this.input.substr(this.pos).startsWith(s)); if (match.length !== 1) { + if (match.length > 1) { + throw new Error("too many maches"); + } throw new Error("Unexpected character."); } this.pos += match[0].length; diff --git a/src/cmd_line/subparsers/quit.ts b/src/cmd_line/subparsers/quit.ts index 2d268bfc0cd..161dba89c83 100644 --- a/src/cmd_line/subparsers/quit.ts +++ b/src/cmd_line/subparsers/quit.ts @@ -1,5 +1,6 @@ import * as node from "../commands/quit"; import {Scanner} from '../scanner'; +import {VimError, ErrorCode} from '../../error'; export function parseQuitCommandArgs(args : string) : node.QuitCommand { if (!args) { @@ -10,12 +11,13 @@ export function parseQuitCommandArgs(args : string) : node.QuitCommand { const c = scanner.next(); if (c === '!') { scannedArgs.bang = true; + scanner.ignore(); } else if (c !== ' ') { - throw new Error('bad command'); + throw VimError.fromCode(ErrorCode.E488); } scanner.skipWhiteSpace(); if (!scanner.isAtEof) { - throw new Error('bad command'); + throw VimError.fromCode(ErrorCode.E488); } return new node.QuitCommand(scannedArgs); } diff --git a/src/error.ts b/src/error.ts index 273447f9a77..0fbc3241b48 100644 --- a/src/error.ts +++ b/src/error.ts @@ -6,12 +6,14 @@ interface VimErrors { export enum ErrorCode { E37 = 37, - E32 = 32 + E32 = 32, + E488 = 488 } const errors : VimErrors = { 32: "No file name", - 37: "No write since last change (add ! to override)" + 37: "No write since last change (add ! to override)", + 488: "Trailing characters" }; diff --git a/test/lexer.test.ts b/test/cmd_line/lexer.test.ts similarity index 87% rename from test/lexer.test.ts rename to test/cmd_line/lexer.test.ts index 6d0899a695d..83fc19790ea 100644 --- a/test/lexer.test.ts +++ b/test/cmd_line/lexer.test.ts @@ -1,9 +1,9 @@ // The module 'assert' provides assertion methods from node import * as assert from 'assert'; -import * as lexer from '../src/cmd_line/lexer'; -import {Token, TokenType} from '../src/cmd_line/token'; +import * as lexer from '../../src/cmd_line/lexer'; +import {Token, TokenType} from '../../src/cmd_line/token'; -suite("Cmd line tests - lexing", () => { +suite("command-line lexer", () => { test("can lex empty string", () => { var tokens = lexer.lex(""); @@ -78,9 +78,15 @@ suite("Cmd line tests - lexing", () => { test("can lex command args", () => { var tokens = lexer.lex("w something"); assert.equal(tokens[0].content, new Token(TokenType.CommandName, "w").content); - assert.equal(tokens[1].content, new Token(TokenType.CommandArgs, "something").content); + assert.equal(tokens[1].content, new Token(TokenType.CommandArgs, " something").content); }); + test("can lex command args with leading whitespace", () => { + var tokens = lexer.lex("q something"); + assert.equal(tokens[0].content, new Token(TokenType.CommandName, "q").content); + assert.equal(tokens[1].content, new Token(TokenType.CommandArgs, " something").content); + }); + test("can lex long command name and args", () => { var tokens = lexer.lex("write12 something here"); assert.equal(tokens[0].content, new Token(TokenType.CommandName, "write").content); diff --git a/test/parser.test.ts b/test/cmd_line/parser.test.ts similarity index 85% rename from test/parser.test.ts rename to test/cmd_line/parser.test.ts index 70dae132965..11bef71f077 100644 --- a/test/parser.test.ts +++ b/test/cmd_line/parser.test.ts @@ -1,9 +1,9 @@ import * as assert from 'assert'; -import * as parser from '../src/cmd_line/parser'; -import * as node from '../src/cmd_line/node'; -import * as token from '../src/cmd_line/token'; +import * as parser from '../../src/cmd_line/parser'; +import * as node from '../../src/cmd_line/node'; +import * as token from '../../src/cmd_line/token'; -suite("Cmd line tests - parser", () => { +suite("command-line parser", () => { test("can parse empty string", () => { var cmd = parser.parse(""); diff --git a/test/cmd_line/scanner.test.ts b/test/cmd_line/scanner.test.ts new file mode 100644 index 00000000000..da354452530 --- /dev/null +++ b/test/cmd_line/scanner.test.ts @@ -0,0 +1,96 @@ +import * as assert from 'assert'; +import {Scanner} from '../../src/cmd_line/scanner'; + +suite("command line scanner", () => { + + test("ctor", () => { + var state = new Scanner("dog"); + assert.equal(state.input, "dog"); + }); + + test("can detect EOF with empty input", () => { + var state = new Scanner(""); + assert.ok(state.isAtEof); + }); + + test("next() returns EOF at EOF", () => { + var state = new Scanner(""); + assert.equal(state.next(), Scanner.EOF); + assert.equal(state.next(), Scanner.EOF); + assert.equal(state.next(), Scanner.EOF); + }); + + test("can scan", () => { + var state = new Scanner("dog"); + assert.equal(state.next(), "d"); + assert.equal(state.next(), "o"); + assert.equal(state.next(), "g"); + assert.equal(state.next(), Scanner.EOF); + }); + + test("can emit", () => { + var state = new Scanner("dog cat"); + state.next(); + state.next(); + state.next(); + assert.equal(state.emit(), "dog"); + state.next(); + state.next(); + state.next(); + state.next(); + assert.equal(state.emit(), " cat"); + }); + + test("can ignore", () => { + var state = new Scanner("dog cat"); + state.next(); + state.next(); + state.next(); + state.next(); + state.ignore(); + state.next(); + state.next(); + state.next(); + assert.equal(state.emit(), "cat"); + }); + + test("can skip whitespace", () => { + var state = new Scanner("dog cat"); + state.next(); + state.next(); + state.next(); + state.ignore(); + state.skipWhiteSpace(); + assert.equal(state.next(), "c"); + }); + + test("can skip whitespace with one char before EOF", () => { + var state = new Scanner("dog c"); + state.next(); + state.next(); + state.next(); + state.ignore(); + state.skipWhiteSpace(); + assert.equal(state.next(), "c"); + }); + + test("can skip whitespace at EOF", () => { + var state = new Scanner("dog "); + state.next(); + state.next(); + state.next(); + state.ignore(); + state.skipWhiteSpace(); + assert.equal(state.next(), Scanner.EOF); + }); + + test("can expect one of a set", () => { + var state = new Scanner("dog cat"); + state.expectOneOf("dog", "mule", "monkey"); + }); + + test("can expect only one of a set", () => { + var state = new Scanner("dog cat"); + assert.throws(() => state.expectOneOf("mule", "monkey")); + }); +}); diff --git a/test/cmd_line/subparser.quit.test.ts b/test/cmd_line/subparser.quit.test.ts new file mode 100644 index 00000000000..0a9b27ef02d --- /dev/null +++ b/test/cmd_line/subparser.quit.test.ts @@ -0,0 +1,44 @@ +// The module 'assert' provides assertion methods from node +import * as assert from 'assert'; + +import {commandParsers} from '../../src/cmd_line/subparser'; + +suite(":quit args parser", () => { + + test("has all aliases", () => { + assert.equal(commandParsers.quit.name, commandParsers.q.name); + }); + + test("can parse empty args", () => { + var args = commandParsers.quit(""); + assert.equal(args.arguments.bang, undefined); + assert.equal(args.arguments.range, undefined); + }); + + test("ignores trailing white space", () => { + var args = commandParsers.quit(" "); + assert.equal(args.arguments.bang, undefined); + assert.equal(args.arguments.range, undefined); + }); + + test("can parse !", () => { + var args = commandParsers.quit("!"); + assert.ok(args.arguments.bang); + assert.equal(args.arguments.range, undefined); + }); + + test("throws if space before !", () => { + assert.throws(() => commandParsers.quit(" !")); + }); + + test("ignores space after !", () => { + + var args = commandParsers.quit("! "); + assert.equal(args.arguments.bang, true); + assert.equal(args.arguments.range, undefined); + }); + + test("throws if bad input", () => { + assert.throws(() => commandParsers.quit("x")); + }); +}); diff --git a/test/subparser.test.ts b/test/cmd_line/subparser.test.ts similarity index 94% rename from test/subparser.test.ts rename to test/cmd_line/subparser.test.ts index 5ded48744e1..88814efaf1c 100644 --- a/test/subparser.test.ts +++ b/test/cmd_line/subparser.test.ts @@ -1,11 +1,11 @@ // The module 'assert' provides assertion methods from node import * as assert from 'assert'; -import {commandParsers} from '../src/cmd_line/subparser'; +import {commandParsers} from '../../src/cmd_line/subparser'; -suite("subparsers - :write args", () => { +suite(":write args parser", () => { - test("parsers for :write are set up correctly", () => { + test("has all aliases", () => { assert.equal(commandParsers.write.name, commandParsers.w.name); }); diff --git a/test/error.test.ts b/test/error.test.ts new file mode 100644 index 00000000000..b3cb90e8180 --- /dev/null +++ b/test/error.test.ts @@ -0,0 +1,35 @@ +// The module 'assert' provides assertion methods from node +import * as assert from 'assert'; +import {VimError, ErrorCode} from '../src/error'; + +suite("ErrorCode", () => { + + test("contains known errors", () => { + assert.equal(ErrorCode.E32, 32); + assert.equal(ErrorCode.E37, 37); + assert.equal(ErrorCode.E488, 488); + }); +}); + +suite("vimError", () => { + + test("ctor", () => { + const e = new VimError(100, "whoof!"); + assert.equal(e.code, 100); + assert.equal(e.message, "whoof!"); + }); + + test("can instantiate known errors", () => { + var e = VimError.fromCode(ErrorCode.E32); + assert.equal(e.code, 32); + assert.equal(e.message, "No file name"); + + e = VimError.fromCode(ErrorCode.E37); + assert.equal(e.code, 37); + assert.equal(e.message, "No write since last change (add ! to override)"); + + e = VimError.fromCode(ErrorCode.E488); + assert.equal(e.code, 488); + assert.equal(e.message, "Trailing characters"); + }); +}); \ No newline at end of file diff --git a/test/scanner.test.ts b/test/scanner.test.ts deleted file mode 100644 index e8344a12e5a..00000000000 --- a/test/scanner.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as assert from 'assert'; -import * as lexerState from '../src/cmd_line/scanner'; - -suite("Cmd line tests - lexer state", () => { - - test("can init lexer state", () => { - var state = new lexerState.Scanner("dog"); - assert.equal(state.input, "dog"); - }); - - test("can detect EOF with empty input", () => { - var state = new lexerState.Scanner(""); - assert.ok(state.isAtEof); - }); - - test("next() returns EOF at EOF", () => { - var state = new lexerState.Scanner(""); - assert.equal(state.next(), lexerState.Scanner.EOF); - assert.equal(state.next(), lexerState.Scanner.EOF); - assert.equal(state.next(), lexerState.Scanner.EOF); - }); - - test("next() can scan", () => { - var state = new lexerState.Scanner("dog"); - assert.equal(state.next(), "d"); - assert.equal(state.next(), "o"); - assert.equal(state.next(), "g"); - assert.equal(state.next(), lexerState.Scanner.EOF); - }); - - test("can emit", () => { - var state = new lexerState.Scanner("dog cat"); - state.next(); - state.next(); - state.next(); - assert.equal(state.emit(), "dog"); - state.next(); - state.next(); - state.next(); - state.next(); - assert.equal(state.emit(), " cat"); - }); - - test("can ignore", () => { - var state = new lexerState.Scanner("dog cat"); - state.next(); - state.next(); - state.next(); - state.next(); - state.ignore(); - state.next(); - state.next(); - state.next(); - assert.equal(state.emit(), "cat"); - }); -});