diff --git a/src/lexer.coffee b/src/lexer.coffee index 92f1bef41b..f65aaec917 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -40,6 +40,7 @@ exports.Lexer = class Lexer @indebt = 0 # The over-indentation at the current level. @outdebt = 0 # The under-outdentation at the current level. @indents = [] # The stack of all current indentation levels. + @indentLiteral = '' # The indentation @ends = [] # The stack for pairing up tokens. @tokens = [] # Stream of parsed tokens in the form `['TYPE', value, location data]`. @seenFor = no # Used to recognize FORIN and FOROF tokens. @@ -351,6 +352,16 @@ exports.Lexer = class Lexer size = indent.length - 1 - indent.lastIndexOf '\n' noNewlines = @unfinished() + newIndentLiteral = if size > 0 then indent[-size..] else '' + unless /^(.?)\1*$/.exec newIndentLiteral + @error 'mixed indentation', offset: indent.length + return indent.length + + minLiteralLength = Math.min newIndentLiteral.length, @indentLiteral.length + if newIndentLiteral[...minLiteralLength] isnt @indentLiteral[...minLiteralLength] + @error 'indentation mismatch', offset: indent.length + return indent.length + if size - @indebt is @indent if noNewlines then @suppressNewlines() else @newlineToken 0 return indent.length @@ -362,6 +373,7 @@ exports.Lexer = class Lexer return indent.length unless @tokens.length @baseIndent = @indent = size + @indentLiteral = newIndentLiteral return indent.length diff = size - @indent + @outdebt @token 'INDENT', diff, indent.length - size, size @@ -369,6 +381,7 @@ exports.Lexer = class Lexer @ends.push {tag: 'OUTDENT'} @outdebt = @indebt = 0 @indent = size + @indentLiteral = newIndentLiteral else if size < @baseIndent @error 'missing indentation', offset: indent.length else @@ -405,6 +418,7 @@ exports.Lexer = class Lexer @token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines @indent = decreasedIndent + @indentLiteral = @indentLiteral[...decreasedIndent] this # Matches and consumes non-meaningful whitespace. Tag the previous token diff --git a/test/compilation.coffee b/test/compilation.coffee index 390375c8f3..95679d1ee6 100644 --- a/test/compilation.coffee +++ b/test/compilation.coffee @@ -76,7 +76,7 @@ test "#2516: Unicode spaces should not be part of identifiers", -> eq 5, {c: 5}[ 'c' ] # A line where every space in non-breaking -  eq 1 + 1, 2   + eq 1 + 1, 2   test "don't accidentally stringify keywords", -> ok (-> this == 'this')() is false diff --git a/test/error_messages.coffee b/test/error_messages.coffee index 812041b224..bf891df3c8 100644 --- a/test/error_messages.coffee +++ b/test/error_messages.coffee @@ -41,18 +41,6 @@ test "compiler error formatting", -> ^^^^ ''' -test "compiler error formatting with mixed tab and space", -> - assertErrorFormat """ - \t if a - \t test - """, - ''' - [stdin]:1:4: error: unexpected if - \t if a - \t ^^ - ''' - - if require? fs = require 'fs' path = require 'path' diff --git a/test/formatting.coffee b/test/formatting.coffee index 31cc282840..144a56c0f6 100644 --- a/test/formatting.coffee +++ b/test/formatting.coffee @@ -254,3 +254,36 @@ test "#1275: allow indentation before closing brackets", -> a = 1 ) eq 1, a + +test "don’t allow mixing of spaces and tabs for indentation", -> + try + CoffeeScript.compile ''' + new Layer + x: 0 + y: 1 + ''' + ok no + catch e + eq 'indentation mismatch', e.message + +test "each code block that starts at indentation 0 can use a different style", -> + doesNotThrow -> + CoffeeScript.compile ''' + new Layer + x: 0 + y: 1 + new Layer + x: 0 + y: 1 + ''' + +test "tabs and spaces cannot be mixed for indentation", -> + try + CoffeeScript.compile ''' + new Layer + x: 0 + y: 1 + ''' + ok no + catch e + eq 'mixed indentation', e.message diff --git a/test/scope.coffee b/test/scope.coffee index c84eabb66b..16115094c4 100644 --- a/test/scope.coffee +++ b/test/scope.coffee @@ -59,21 +59,21 @@ test "#1183: super + fat arrows", -> dolater = (cb) -> cb() class A - constructor: -> - @_i = 0 - foo : (cb) -> - dolater => - @_i += 1 - cb() + constructor: -> + @_i = 0 + foo : (cb) -> + dolater => + @_i += 1 + cb() class B extends A - constructor : -> - super - foo : (cb) -> - dolater => - dolater => - @_i += 2 - super cb + constructor : -> + super + foo : (cb) -> + dolater => + dolater => + @_i += 2 + super cb b = new B b.foo => eq b._i, 3