From 98068611b12420671bf64601aefb5b046ed9882d Mon Sep 17 00:00:00 2001 From: Eelco Lempsink Date: Tue, 20 Sep 2016 23:06:44 +0200 Subject: [PATCH] Make sure the indentation is consistent with the previous level. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This prevents mixing spaces and tabs in the same ‘block’ of code. Mixing is still allowed if each line uses the same mix and if the indentation level returns to 0. This breaks the literate coffeescript test that mixes spaces and tabs. --- src/lexer.coffee | 10 ++++++++++ test/compilation.coffee | 2 +- test/formatting.coffee | 30 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/lexer.coffee b/src/lexer.coffee index e0d3febd9b..e529a47f26 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. @@ -347,6 +348,12 @@ exports.Lexer = class Lexer size = indent.length - 1 - indent.lastIndexOf '\n' noNewlines = @unfinished() + newIndentLiteral = if size > 0 then indent[-size..] else '' + 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 @@ -358,6 +365,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 @@ -365,6 +373,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 @@ -401,6 +410,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/formatting.coffee b/test/formatting.coffee index 31cc282840..10b9927cba 100644 --- a/test/formatting.coffee +++ b/test/formatting.coffee @@ -254,3 +254,33 @@ 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 "indentation can be a mix of spaces and tabs, if each line is the same", -> + doesNotThrow -> + CoffeeScript.compile ''' + new Layer + x: 0 + y: 1 + ''' + +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 + '''