Author: Ludovic Jozeau
Date: 2021-10-30T17:45:39+01:00
New Revision: 931d20c5db05fc2e2f4071cc086fa3ff1b4aebdd

URL: 
https://github.com/llvm/llvm-project/commit/931d20c5db05fc2e2f4071cc086fa3ff1b4aebdd
DIFF: 
https://github.com/llvm/llvm-project/commit/931d20c5db05fc2e2f4071cc086fa3ff1b4aebdd.diff

LOG: [docs][clang-format] warn on \code block indentation error

There is an indentation issue in Format.h causing the html to not render 
correctly
It's a `\code` block in the documentation of SpacesInLineComment

- fix intentation of `SpacesInLineComment`
- warn on indentation error
- also warn on `\code` `\endcode` mismatch
- generate precise warnings
- fix some minor and style issues:
  - avoid confusion with the built-in `type` function
  - fix wrong print on `os.sys.stderr` (instead of `sys.stderr`)
  - use `with as` pattern for files

Reviewed By: MyDeveloperDay, HazardyKnusperkeks

Differential Revision: https://reviews.llvm.org/D112572

Added: 
    

Modified: 
    clang/docs/ClangFormatStyleOptions.rst
    clang/docs/tools/dump_format_style.py
    clang/include/clang/Format/Format.h

Removed: 
    


################################################################################
diff  --git a/clang/docs/ClangFormatStyleOptions.rst 
b/clang/docs/ClangFormatStyleOptions.rst
index e2e044145018b..e9a381c93a46b 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -3807,30 +3807,34 @@ the configuration (without a prefix: ``Auto``).
   How many spaces are allowed at the start of a line comment. To disable the
   maximum set it to ``-1``, apart from that the maximum takes precedence
   over the minimum.
-  Minimum = 1 Maximum = -1
-  // One space is forced
 
-  //  but more spaces are possible
+  .. code-block:: c++
+
+    Minimum = 1
+    Maximum = -1
+    // One space is forced
+
+    //  but more spaces are possible
 
-  Minimum = 0
-  Maximum = 0
-  //Forces to start every comment directly after the slashes
+    Minimum = 0
+    Maximum = 0
+    //Forces to start every comment directly after the slashes
 
   Note that in line comment sections the relative indent of the subsequent
   lines is kept, that means the following:
 
   .. code-block:: c++
 
-  before:                                   after:
-  Minimum: 1
-  //if (b) {                                // if (b) {
-  //  return true;                          //   return true;
-  //}                                       // }
+    before:                                   after:
+    Minimum: 1
+    //if (b) {                                // if (b) {
+    //  return true;                          //   return true;
+    //}                                       // }
 
-  Maximum: 0
-  /// List:                                 ///List:
-  ///  - Foo                                /// - Foo
-  ///    - Bar                              ///   - Bar
+    Maximum: 0
+    /// List:                                 ///List:
+    ///  - Foo                                /// - Foo
+    ///    - Bar                              ///   - Bar
 
   Nested configuration flags:
 

diff  --git a/clang/docs/tools/dump_format_style.py 
b/clang/docs/tools/dump_format_style.py
index a86b47bb27605..d04920987ec67 100755
--- a/clang/docs/tools/dump_format_style.py
+++ b/clang/docs/tools/dump_format_style.py
@@ -6,6 +6,8 @@
 import inspect
 import os
 import re
+import sys
+from io import TextIOWrapper
 from typing import Set
 
 CLANG_DIR = os.path.join(os.path.dirname(__file__), '../..')
@@ -40,7 +42,7 @@ def register_plural(singular: str, plural: str):
     lineno = ''
     if cf and cf.f_back:
       lineno = ':' + str(cf.f_back.f_lineno)
-    print(f'{__file__}{lineno} check if plural of {singular} is {plural}', 
file=os.sys.stderr)
+    print(f'{__file__}{lineno} check if plural of {singular} is {plural}', 
file=sys.stderr)
   return plural
 
 def pluralize(word: str):
@@ -80,16 +82,16 @@ def doxygen2rst(text):
   return text
 
 def indent(text, columns, indent_first_line=True):
-  indent = ' ' * columns
-  s = re.sub(r'\n([^\n])', '\n' + indent + '\\1', text, flags=re.S)
+  indent_str = ' ' * columns
+  s = re.sub(r'\n([^\n])', '\n' + indent_str + '\\1', text, flags=re.S)
   if not indent_first_line or s.startswith('\n'):
     return s
-  return indent + s
+  return indent_str + s
 
 class Option(object):
-  def __init__(self, name, type, comment, version):
+  def __init__(self, name, opt_type, comment, version):
     self.name = name
-    self.type = type
+    self.type = opt_type
     self.comment = comment.strip()
     self.enum = None
     self.nested_struct = None
@@ -148,8 +150,8 @@ def __str__(self):
     s = '\n* ``%s %s``\n%s' % (to_yaml_type(self.type), self.name,
                                  doxygen2rst(indent(self.comment, 2)))
     s += indent('\nPossible values:\n\n', 2)
-    s += indent('\n'.join(map(str, self.values)),2)
-    return s;
+    s += indent('\n'.join(map(str, self.values)), 2)
+    return s
 
 class EnumValue(object):
   def __init__(self, name, comment, config):
@@ -163,148 +165,191 @@ def __str__(self):
         re.sub('.*_', '', self.config),
         doxygen2rst(indent(self.comment, 2)))
 
-def clean_comment_line(line):
-  match = re.match(r'^/// (?P<indent> +)?\\code(\{.(?P<lang>\w+)\})?$', line)
-  if match:
-    indent = match.group('indent')
-    if not indent:
-      indent = ''
-    lang = match.group('lang')
-    if not lang:
-      lang = 'c++'
-    return '\n%s.. code-block:: %s\n\n' % (indent, lang)
-
-  endcode_match = re.match(r'^/// +\\endcode$', line)
-  if endcode_match:
-    return ''
-
-  match = re.match(r'^/// \\warning$', line)
-  if match:
-    return '\n.. warning:: \n\n'
-
-  endwarning_match = re.match(r'^/// +\\endwarning$', line)
-  if endwarning_match:
-    return ''
-  return line[4:] + '\n'
-
-def read_options(header):
-  class State(object):
-    BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComment, \
-    InFieldComment, InEnum, InEnumMemberComment = range(8)
-  state = State.BeforeStruct
-
-  options = []
-  enums = {}
-  nested_structs = {}
-  comment = ''
-  enum = None
-  nested_struct = None
-  version = None
-
-  for line in header:
-    line = line.strip()
-    if state == State.BeforeStruct:
-      if line == 'struct FormatStyle {' or line == 'struct IncludeStyle {':
-        state = State.InStruct
-    elif state == State.InStruct:
-      if line.startswith('///'):
-        state = State.InFieldComment
-        comment = clean_comment_line(line)
-      elif line == '};':
-        state = State.Finished
-        break
-    elif state == State.InFieldComment:
-      if line.startswith(r'/// \version'):
-        match = re.match(r'/// \\version\s*(?P<version>[0-9.]+)*',line)
-        if match:
-            version = match.group('version')
-      elif line.startswith('///'):
-        comment += clean_comment_line(line)
-      elif line.startswith('enum'):
-        state = State.InEnum
-        name = re.sub(r'enum\s+(\w+)\s*(:((\s*\w+)+)\s*)?\{', '\\1', line)
-        enum = Enum(name, comment)
-      elif line.startswith('struct'):
-        state = State.InNestedStruct
-        name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line)
-        nested_struct = NestedStruct(name, comment)
-      elif line.endswith(';'):
-        state = State.InStruct
-        field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);',
-                                          line).groups()
-
-        if not version:
-            print('Warning missing version for ', field_name)
-        option = Option(str(field_name), str(field_type), comment, version)
-        options.append(option)
-        version=None
-      else:
-        raise Exception('Invalid format, expected comment, field or 
enum\n'+line)
-    elif state == State.InNestedStruct:
-      if line.startswith('///'):
-        state = State.InNestedFieldComment
-        comment = clean_comment_line(line)
-      elif line == '};':
-        state = State.InStruct
-        nested_structs[nested_struct.name] = nested_struct
-    elif state == State.InNestedFieldComment:
-      if line.startswith('///'):
-        comment += clean_comment_line(line)
+
+class OptionsReader:
+  def __init__(self, header: TextIOWrapper):
+    self.header = header
+    self.in_code_block = False
+    self.code_indent = 0
+    self.lineno = 0
+    self.last_err_lineno = -1
+
+  def __file_path(self):
+    return os.path.relpath(self.header.name)
+
+  def __print_line(self, line: str):
+    print(f'{self.lineno:>6} | {line}', file=sys.stderr)
+
+  def __warning(self, msg: str, line: str):
+    print(f'{self.__file_path()}:{self.lineno}: warning: {msg}:', 
file=sys.stderr)
+    self.__print_line(line)
+
+  def __clean_comment_line(self, line: str):
+    match = re.match(r'^/// (?P<indent> +)?\\code(\{.(?P<lang>\w+)\})?$', line)
+    if match:
+      if self.in_code_block:
+        self.__warning('`\\code` in another `\\code`', line)
+      self.in_code_block = True
+      indent_str = match.group('indent')
+      if not indent_str:
+        indent_str = ''
+      self.code_indent = len(indent_str)
+      lang = match.group('lang')
+      if not lang:
+        lang = 'c++'
+      return f'\n{indent_str}.. code-block:: {lang}\n\n'
+
+    endcode_match = re.match(r'^/// +\\endcode$', line)
+    if endcode_match:
+      if not self.in_code_block:
+        self.__warning('no correct `\\code` found before this `\\endcode`', 
line)
+      self.in_code_block = False
+      return ''
+
+    # check code block indentation
+    if (self.in_code_block and not line == '///' and not
+        line.startswith('///  ' + ' ' * self.code_indent)):
+      if self.last_err_lineno == self.lineno - 1:
+        self.__print_line(line)
       else:
-        state = State.InNestedStruct
-        field_type, field_name = 
re.match(r'([<>:\w(,\s)]+)\s+(\w+);',line).groups()
-        if field_type in enums:
-            
nested_struct.values.append(NestedEnum(field_name,field_type,comment,enums[field_type].values))
+        self.__warning('code block should be indented', line)
+      self.last_err_lineno = self.lineno
+
+    match = re.match(r'^/// \\warning$', line)
+    if match:
+      return '\n.. warning:: \n\n'
+
+    endwarning_match = re.match(r'^/// +\\endwarning$', line)
+    if endwarning_match:
+      return ''
+    return line[4:] + '\n'
+
+  def read_options(self):
+    class State:
+      BeforeStruct, Finished, InStruct, InNestedStruct, InNestedFieldComment, \
+        InFieldComment, InEnum, InEnumMemberComment = range(8)
+    state = State.BeforeStruct
+
+    options = []
+    enums = {}
+    nested_structs = {}
+    comment = ''
+    enum = None
+    nested_struct = None
+    version = None
+
+    for line in self.header:
+      self.lineno += 1
+      line = line.strip()
+      if state == State.BeforeStruct:
+        if line in ('struct FormatStyle {', 'struct IncludeStyle {'):
+          state = State.InStruct
+      elif state == State.InStruct:
+        if line.startswith('///'):
+          state = State.InFieldComment
+          comment = self.__clean_comment_line(line)
+        elif line == '};':
+          state = State.Finished
+          break
+      elif state == State.InFieldComment:
+        if line.startswith(r'/// \version'):
+          match = re.match(r'/// \\version\s*(?P<version>[0-9.]+)*', line)
+          if match:
+            version = match.group('version')
+        elif line.startswith('///'):
+          comment += self.__clean_comment_line(line)
+        elif line.startswith('enum'):
+          state = State.InEnum
+          name = re.sub(r'enum\s+(\w+)\s*(:((\s*\w+)+)\s*)?\{', '\\1', line)
+          enum = Enum(name, comment)
+        elif line.startswith('struct'):
+          state = State.InNestedStruct
+          name = re.sub(r'struct\s+(\w+)\s*\{', '\\1', line)
+          nested_struct = NestedStruct(name, comment)
+        elif line.endswith(';'):
+          state = State.InStruct
+          field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);',
+                                            line).groups()
+
+          if not version:
+            self.__warning(f'missing version for {field_name}', line)
+          option = Option(str(field_name), str(field_type), comment, version)
+          options.append(option)
+          version = None
+        else:
+          raise Exception('Invalid format, expected comment, field or enum\n' 
+ line)
+      elif state == State.InNestedStruct:
+        if line.startswith('///'):
+          state = State.InNestedFieldComment
+          comment = self.__clean_comment_line(line)
+        elif line == '};':
+          state = State.InStruct
+          nested_structs[nested_struct.name] = nested_struct
+      elif state == State.InNestedFieldComment:
+        if line.startswith('///'):
+          comment += self.__clean_comment_line(line)
         else:
+          state = State.InNestedStruct
+          field_type, field_name = re.match(r'([<>:\w(,\s)]+)\s+(\w+);', 
line).groups()
+          if field_type in enums:
+            nested_struct.values.append(NestedEnum(field_name,
+                                                   field_type,
+                                                   comment,
+                                                   enums[field_type].values))
+          else:
             nested_struct.values.append(NestedField(field_type + " " + 
field_name, comment))
 
-    elif state == State.InEnum:
-      if line.startswith('///'):
-        state = State.InEnumMemberComment
-        comment = clean_comment_line(line)
-      elif line == '};':
-        state = State.InStruct
-        enums[enum.name] = enum
-      else:
-        # Enum member without documentation. Must be documented where the enum
-        # is used.
-        pass
-    elif state == State.InEnumMemberComment:
-      if line.startswith('///'):
-        comment += clean_comment_line(line)
-      else:
-        state = State.InEnum
-        val = line.replace(',', '')
-        pos = val.find(" // ")
-        if (pos != -1):
-            config = val[pos+4:]
+      elif state == State.InEnum:
+        if line.startswith('///'):
+          state = State.InEnumMemberComment
+          comment = self.__clean_comment_line(line)
+        elif line == '};':
+          state = State.InStruct
+          enums[enum.name] = enum
+        else:
+          # Enum member without documentation. Must be documented where the 
enum
+          # is used.
+          pass
+      elif state == State.InEnumMemberComment:
+        if line.startswith('///'):
+          comment += self.__clean_comment_line(line)
+        else:
+          state = State.InEnum
+          val = line.replace(',', '')
+          pos = val.find(" // ")
+          if pos != -1:
+            config = val[pos + 4:]
             val = val[:pos]
+          else:
+            config = val
+          enum.values.append(EnumValue(val, comment, config))
+    if state != State.Finished:
+      raise Exception('Not finished by the end of file')
+
+    for option in options:
+      if option.type not in ['bool', 'unsigned', 'int', 'std::string',
+                             'std::vector<std::string>',
+                             'std::vector<IncludeCategory>',
+                             'std::vector<RawStringFormat>']:
+        if option.type in enums:
+          option.enum = enums[option.type]
+        elif option.type in nested_structs:
+          option.nested_struct = nested_structs[option.type]
         else:
-            config = val;
-        enum.values.append(EnumValue(val, comment,config))
-  if state != State.Finished:
-    raise Exception('Not finished by the end of file')
-
-  for option in options:
-    if not option.type in ['bool', 'unsigned', 'int', 'std::string',
-                           'std::vector<std::string>',
-                           'std::vector<IncludeCategory>',
-                           'std::vector<RawStringFormat>']:
-      if option.type in enums:
-        option.enum = enums[option.type]
-      elif option.type in nested_structs:
-        option.nested_struct = nested_structs[option.type]
-      else:
-        raise Exception('Unknown type: %s' % option.type)
-  return options
+          raise Exception('Unknown type: %s' % option.type)
+    return options
+
 
-options = read_options(open(FORMAT_STYLE_FILE))
-options += read_options(open(INCLUDE_STYLE_FILE))
+with open(FORMAT_STYLE_FILE) as f:
+  opts = OptionsReader(f).read_options()
+with open(INCLUDE_STYLE_FILE) as f:
+  opts += OptionsReader(f).read_options()
 
-options = sorted(options, key=lambda x: x.name)
-options_text = '\n\n'.join(map(str, options))
+opts = sorted(opts, key=lambda x: x.name)
+options_text = '\n\n'.join(map(str, opts))
 
-contents = open(DOC_FILE).read()
+with open(DOC_FILE) as f:
+  contents = f.read()
 
 contents = substitute(contents, 'FORMAT_STYLE_OPTIONS', options_text)
 

diff  --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index 77565797b28b5..3fce6eade27f3 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -3430,29 +3430,31 @@ struct FormatStyle {
   /// How many spaces are allowed at the start of a line comment. To disable 
the
   /// maximum set it to ``-1``, apart from that the maximum takes precedence
   /// over the minimum.
-  /// \code Minimum = 1 Maximum = -1
-  /// // One space is forced
+  /// \code
+  ///   Minimum = 1
+  ///   Maximum = -1
+  ///   // One space is forced
   ///
-  /// //  but more spaces are possible
+  ///   //  but more spaces are possible
   ///
-  /// Minimum = 0
-  /// Maximum = 0
-  /// //Forces to start every comment directly after the slashes
+  ///   Minimum = 0
+  ///   Maximum = 0
+  ///   //Forces to start every comment directly after the slashes
   /// \endcode
   ///
   /// Note that in line comment sections the relative indent of the subsequent
   /// lines is kept, that means the following:
   /// \code
-  /// before:                                   after:
-  /// Minimum: 1
-  /// //if (b) {                                // if (b) {
-  /// //  return true;                          //   return true;
-  /// //}                                       // }
+  ///   before:                                   after:
+  ///   Minimum: 1
+  ///   //if (b) {                                // if (b) {
+  ///   //  return true;                          //   return true;
+  ///   //}                                       // }
   ///
-  /// Maximum: 0
-  /// /// List:                                 ///List:
-  /// ///  - Foo                                /// - Foo
-  /// ///    - Bar                              ///   - Bar
+  ///   Maximum: 0
+  ///   /// List:                                 ///List:
+  ///   ///  - Foo                                /// - Foo
+  ///   ///    - Bar                              ///   - Bar
   /// \endcode
   /// \version 14
   SpacesInLineComment SpacesInLineCommentPrefix;


        
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to