changeset 55921:353e367c651f string-tapas

Test for inconsistent use of indentation characters
author jlaskey
date Fri, 17 May 2019 13:12:23 -0300
parents 36fa96af83f7
children eea6c12236ce
files src/java.base/share/classes/java/lang/String.java src/java.base/share/classes/java/lang/StringLatin1.java src/java.base/share/classes/java/lang/StringUTF16.java src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties
diffstat 7 files changed, 103 insertions(+), 173 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/lang/String.java	Thu May 16 11:31:31 2019 -0300
+++ b/src/java.base/share/classes/java/lang/String.java	Fri May 17 13:12:23 2019 -0300
@@ -36,6 +36,7 @@
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Formatter;
+import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Optional;
@@ -2770,11 +2771,6 @@
         return indexOfNonWhitespace() == length();
     }
 
-    private Stream<String> lines(int maxLeading, int maxTrailing) {
-        return isLatin1() ? StringLatin1.lines(value, maxLeading, maxTrailing)
-                          : StringUTF16.lines(value, maxLeading, maxTrailing);
-    }
-
     /**
      * Returns a stream of lines extracted from this string,
      * separated by line terminators.
@@ -2806,7 +2802,7 @@
      * @since 11
      */
     public Stream<String> lines() {
-        return lines(0, 0);
+        return isLatin1() ? StringLatin1.lines(value) : StringUTF16.lines(value);
     }
 
     /**
@@ -2849,19 +2845,16 @@
         if (isEmpty()) {
             return "";
         }
-        return indentStream(lines(), n).collect(Collectors.joining("\n", "", "\n"));
-    }
-
-    private static Stream<String> indentStream(Stream<String> stream, int n) {
+        Stream<String> stream = lines();
         if (n > 0) {
             final String spaces = " ".repeat(n);
-            return stream.map(s -> spaces + s);
+            stream = stream.map(s -> spaces + s);
         } else if (n == Integer.MIN_VALUE) {
-            return stream.map(s -> s.stripLeading());
+            stream = stream.map(s -> s.stripLeading());
         } else if (n < 0) {
-            return stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
+            stream = stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
         }
-        return stream;
+        return stream.collect(Collectors.joining("\n", "", "\n"));
     }
 
     private int indexOfNonWhitespace() {
@@ -2932,35 +2925,40 @@
      */
     @Deprecated(forRemoval=true, since="13")
     public String stripIndent() {
-        if (isEmpty()) {
+        int length = length();
+        if (length == 0) {
             return "";
         }
+        char lastChar = charAt(length - 1);
+        boolean optOut = lastChar == '\n' || lastChar == '\r';
+        List<String> lines = lines().collect(Collectors.toList());
+        final int outdent = optOut ? 0 : outdent(lines);
+        return lines.stream()
+            .map(line -> {
+                int firstNonWhitespace = line.indexOfNonWhitespace();
+                int lastNonWhitespace = line.lastIndexOfNonWhitespace();
+                return firstNonWhitespace > lastNonWhitespace
+                    ? "" : line.substring(Math.min(outdent, firstNonWhitespace), lastNonWhitespace);
+            })
+            .collect(Collectors.joining("\n", "", optOut ? "\n" : ""));
+    }
+
+    private static int outdent(List<String> lines) {
+        // Note: outdent is guaranteed to be zero or positive number.
+        // If there isn't a non-blank line then the last must be blank
         int outdent = Integer.MAX_VALUE;
-        boolean isNewLine = true;
-        int whitespaceCount = 0;
-        for (int i = 0; i < length(); i++) {
-            char ch = charAt(i);
-            if (ch == '\n' || ch == '\r') {
-                whitespaceCount = 0;
-                isNewLine = true;
-            } else if (Character.isWhitespace(ch)) {
-                whitespaceCount++;
-            } else {
-                if (isNewLine) {
-                    outdent = Math.min(outdent, whitespaceCount);
-                }
-                isNewLine = false;
-                whitespaceCount = 0;
+        for (String line : lines) {
+            int leadingWhitespace = line.indexOfNonWhitespace();
+            if (leadingWhitespace != line.length()) {
+                outdent = Integer.min(outdent, leadingWhitespace);
             }
         }
-        if (isNewLine) {
-            outdent = Math.min(outdent, whitespaceCount);
+        String lastLine = lines.get(lines.size() - 1);
+        if (lastLine.isBlank()) {
+            outdent = Integer.min(outdent, lastLine.length());
         }
-        boolean blankLastLine = isNewLine && whitespaceCount == 0;
-        return indentStream(lines(), -outdent)
-                    .map(s -> s.stripTrailing())
-                    .collect(Collectors.joining("\n", "", blankLastLine ? "\n" : ""));
-    }
+        return outdent;
+   }
 
     /**
      * Translates all escape sequences in the string into characters
--- a/src/java.base/share/classes/java/lang/StringLatin1.java	Thu May 16 11:31:31 2019 -0300
+++ b/src/java.base/share/classes/java/lang/StringLatin1.java	Fri May 17 13:12:23 2019 -0300
@@ -681,76 +681,10 @@
         static LinesSpliterator spliterator(byte[] value) {
             return new LinesSpliterator(value, 0, value.length);
         }
-
-        static LinesSpliterator spliterator(byte[] value, int leading, int trailing) {
-            int length = value.length;
-            int left = 0;
-            int index;
-            for (int l = 0; l < leading; l++) {
-                index = skipBlankForward(value, left, length);
-                if (index == left) {
-                    break;
-                }
-                left = index;
-            }
-            int right = length;
-            for (int t = 0; t < trailing; t++) {
-                index = skipBlankBackward(value, left, right);
-                if (index == right) {
-                    break;
-                }
-                right = index;
-            }
-            return new LinesSpliterator(value, left, right - left);
-        }
-
-        private static int skipBlankForward(byte[] value, int start, int length) {
-            int index = start;
-            while (index < length) {
-                char ch = getChar(value, index++);
-                if (ch == '\n') {
-                    return index;
-                }
-                if (ch == '\r') {
-                    if (index < length && getChar(value, index) == '\n') {
-                        return index + 1;
-                    }
-                    return index;
-                }
-                if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) {
-                    return start;
-                }
-            }
-            return length;
-        }
-
-        private static int skipBlankBackward(byte[] value, int start, int fence) {
-            int index = fence;
-            if (start < index && getChar(value, index - 1) == '\n') {
-                index--;
-            }
-            if (start < index && getChar(value, index - 1) == '\r') {
-                index--;
-            }
-            while (start < index) {
-                char ch = getChar(value, --index);
-                if (ch == '\r' || ch == '\n') {
-                    return index + 1;
-                }
-                if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) {
-                    return fence;
-                }
-            }
-            return start;
-        }
     }
 
-    static Stream<String> lines(byte[] value, int leading, int trailing) {
-        if (leading == 0 && trailing == 0) {
-            return StreamSupport.stream(LinesSpliterator.spliterator(value), false);
-        } else {
-            return StreamSupport.stream(LinesSpliterator.spliterator(value, leading, trailing), false);
-        }
+    static Stream<String> lines(byte[] value) {
+        return StreamSupport.stream(LinesSpliterator.spliterator(value), false);
     }
 
     public static void putChar(byte[] val, int index, int c) {
--- a/src/java.base/share/classes/java/lang/StringUTF16.java	Thu May 16 11:31:31 2019 -0300
+++ b/src/java.base/share/classes/java/lang/StringUTF16.java	Fri May 17 13:12:23 2019 -0300
@@ -1002,76 +1002,10 @@
         static LinesSpliterator spliterator(byte[] value) {
             return new LinesSpliterator(value, 0, value.length >>> 1);
         }
-
-        static LinesSpliterator spliterator(byte[] value, int leading, int trailing) {
-            int length = value.length >>> 1;
-            int left = 0;
-            int index;
-            for (int l = 0; l < leading; l++) {
-                index = skipBlankForward(value, left, length);
-                if (index == left) {
-                    break;
-                }
-                left = index;
-            }
-            int right = length;
-            for (int t = 0; t < trailing; t++) {
-                index = skipBlankBackward(value, left, right);
-                if (index == right) {
-                    break;
-                }
-                right = index;
-            }
-            return new LinesSpliterator(value, left, right - left);
-        }
-
-        private static int skipBlankForward(byte[] value, int start, int length) {
-            int index = start;
-            while (index < length) {
-                char ch = getChar(value, index++);
-                if (ch == '\n') {
-                    return index;
-                }
-                if (ch == '\r') {
-                    if (index < length && getChar(value, index) == '\n') {
-                        return index + 1;
-                    }
-                    return index;
-                }
-                if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) {
-                    return start;
-                }
-            }
-            return length;
-        }
-
-        private static int skipBlankBackward(byte[] value, int start, int fence) {
-            int index = fence;
-            if (start < index && getChar(value, index - 1) == '\n') {
-                index--;
-            }
-            if (start < index && getChar(value, index - 1) == '\r') {
-                index--;
-            }
-            while (start < index) {
-                char ch = getChar(value, --index);
-                if (ch == '\r' || ch == '\n') {
-                    return index + 1;
-                }
-                if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) {
-                    return fence;
-                }
-            }
-            return start;
-        }
     }
 
-    static Stream<String> lines(byte[] value, int leading, int trailing) {
-        if (leading == 0 && trailing == 0) {
-            return StreamSupport.stream(LinesSpliterator.spliterator(value), false);
-        } else {
-            return StreamSupport.stream(LinesSpliterator.spliterator(value, leading, trailing), false);
-        }
+    static Stream<String> lines(byte[] value) {
+        return StreamSupport.stream(LinesSpliterator.spliterator(value), false);
     }
 
     private static void putChars(byte[] val, int index, char[] str, int off, int end) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java	Thu May 16 11:31:31 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java	Fri May 17 13:12:23 2019 -0300
@@ -275,6 +275,11 @@
         STATIC("static"),
 
         /**
+         * Warn about issues relating to use of text blocks
+         */
+        TEXT_BLOCKS("text-blocks"),
+
+        /**
          * Warn about issues relating to use of try blocks (i.e. try-with-resources)
          */
         TRY("try"),
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java	Thu May 16 11:31:31 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java	Fri May 17 13:12:23 2019 -0300
@@ -25,11 +25,14 @@
 
 package com.sun.tools.javac.parser;
 
+import com.sun.tools.javac.code.Lint;
+import com.sun.tools.javac.code.Lint.LintCategory;
 import com.sun.tools.javac.code.Preview;
 import com.sun.tools.javac.code.Source;
 import com.sun.tools.javac.code.Source.Feature;
 import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
 import com.sun.tools.javac.resources.CompilerProperties.Errors;
+import com.sun.tools.javac.resources.CompilerProperties.Warnings;
 import com.sun.tools.javac.util.*;
 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
 
@@ -95,6 +98,11 @@
 
     protected ScannerFactory fac;
 
+    // The set of lint options currently in effect. It is initialized
+    // from the context, and then is set/reset as needed by Attr as it
+    // visits all the various parts of the trees during attribution.
+    protected Lint lint;
+
     private static final boolean hexFloatsWork = hexFloatsWork();
     private static boolean hexFloatsWork() {
         try {
@@ -130,6 +138,7 @@
         this.source = fac.source;
         this.preview = fac.preview;
         this.reader = reader;
+        this.lint = fac.lint;
     }
 
     protected void checkSourceLevel(int pos, Feature feature) {
@@ -159,6 +168,10 @@
         errPos = pos;
     }
 
+    protected void lexWarning(int pos, JCDiagnostic.Warning key) {
+        log.warning(pos, key);
+    }
+
     /** Read next character in character or string literal and copy into sbuf.
      */
     private void scanLitChar(int pos) {
@@ -274,6 +287,42 @@
             return hasSupport;
         }
 
+        private static int indent(String line) {
+            return line.length() - line.stripLeading().length();
+        }
+
+        static boolean checkIncidentalWhitespace(String string) {
+            if (string.isEmpty()) {
+                return true;
+            }
+            char lastChar = string.charAt(string.length() - 1);
+            boolean optOut = lastChar == '\n' || lastChar == '\r';
+            if (optOut)       {
+                return true;
+            }
+            String[] lines = string.split("\\R");
+            int length = lines.length;
+            String lastLine = lines[length - 1];
+            int outdent = indent(lastLine);
+            for (String line : lines) {
+                if (!line.isBlank()) {
+                    outdent = Integer.min(outdent, indent(line));
+                    if (outdent == 0) {
+                        return true;
+                    }
+                }
+            }
+            if (outdent != 0) {
+                String start = lastLine.substring(0, outdent);
+                for (String line : lines) {
+                    if (!line.isBlank() && !line.startsWith(start)) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
         static String stripIndent(String string) {
             try {
                 string = (String)stripIndent.invoke(string);
@@ -882,6 +931,10 @@
                 case STRING: {
                     String string = reader.chars();
                     if (shouldStripIndent) {
+                        if (lint.isEnabled(LintCategory.TEXT_BLOCKS) &&
+                            !TextBlockSupport.checkIncidentalWhitespace(string)) {
+                            lexWarning(pos, Warnings.IncidentalWhitespaceInconsistent);
+                        }
                         string = TextBlockSupport.stripIndent(string);
                         shouldStripIndent = false;
                     }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java	Thu May 16 11:31:31 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java	Fri May 17 13:12:23 2019 -0300
@@ -27,6 +27,7 @@
 
 import java.nio.CharBuffer;
 
+import com.sun.tools.javac.code.Lint;
 import com.sun.tools.javac.code.Preview;
 import com.sun.tools.javac.code.Source;
 import com.sun.tools.javac.util.Context;
@@ -59,6 +60,7 @@
     final Source source;
     final Preview preview;
     final Tokens tokens;
+    final Lint lint;
 
     /** Create a new scanner factory. */
     protected ScannerFactory(Context context) {
@@ -68,6 +70,7 @@
         this.source = Source.instance(context);
         this.preview = Preview.instance(context);
         this.tokens = Tokens.instance(context);
+        this.lint = Lint.instance(context);
     }
 
     public Scanner newScanner(CharSequence input, boolean keepDocComments) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Thu May 16 11:31:31 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties	Fri May 17 13:12:23 2019 -0300
@@ -626,6 +626,9 @@
 compiler.err.illegal.text.block.open=\
     illegal text block open delimiter sequence, missing line terminator
 
+compiler.warn.incidental.whitespace.inconsistent=\
+    inconsistent white space indentation
+
 compiler.err.illegal.nonascii.digit=\
     illegal non-ASCII digit