diff --git a/.clang-format b/.clang-format index d94a2926e..a5890c8bf 100644 --- a/.clang-format +++ b/.clang-format @@ -1,75 +1,75 @@ --- -Language: Cpp +Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveAssignments: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false + AcrossComments: false + AlignCompound: false AlignFunctionDeclarations: false AlignFunctionPointers: false - PadOperators: true + PadOperators: true AlignConsecutiveBitFields: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false + AcrossComments: false + AlignCompound: false AlignFunctionDeclarations: false AlignFunctionPointers: false - PadOperators: false + PadOperators: false AlignConsecutiveDeclarations: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false + AcrossComments: false + AlignCompound: false AlignFunctionDeclarations: true AlignFunctionPointers: false - PadOperators: false + PadOperators: false AlignConsecutiveMacros: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false + AcrossComments: false + AlignCompound: false AlignFunctionDeclarations: false AlignFunctionPointers: false - PadOperators: false + PadOperators: false AlignConsecutiveShortCaseStatements: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false + AcrossComments: false AlignCaseArrows: false AlignCaseColons: false AlignConsecutiveTableGenBreakingDAGArgColons: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false + AcrossComments: false + AlignCompound: false AlignFunctionDeclarations: false AlignFunctionPointers: false - PadOperators: false + PadOperators: false AlignConsecutiveTableGenCondOperatorColons: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false + AcrossComments: false + AlignCompound: false AlignFunctionDeclarations: false AlignFunctionPointers: false - PadOperators: false + PadOperators: false AlignConsecutiveTableGenDefinitionColons: - Enabled: false + Enabled: false AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false + AcrossComments: false + AlignCompound: false AlignFunctionDeclarations: false AlignFunctionPointers: false - PadOperators: false + PadOperators: false AlignEscapedNewlines: Right -AlignOperands: Align +AlignOperands: Align AlignTrailingComments: - Kind: Always - OverEmptyLines: 0 + Kind: Always + OverEmptyLines: 0 AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowBreakBeforeNoexceptSpecifier: Never @@ -89,21 +89,21 @@ BinPackArguments: false BinPackParameters: false BitFieldColonSpacing: Both BraceWrapping: - AfterCaseLabel: false - AfterClass: false + AfterCaseLabel: false + AfterClass: false AfterControlStatement: Never - AfterEnum: false + AfterEnum: false AfterExternBlock: false - AfterFunction: true - AfterNamespace: false + AfterFunction: true + AfterNamespace: false AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - BeforeCatch: false - BeforeElse: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false BeforeLambdaBody: false - BeforeWhile: false - IndentBraces: false + BeforeWhile: false + IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: false SplitEmptyNamespace: false @@ -111,7 +111,7 @@ BreakAdjacentStringLiterals: true BreakAfterAttributes: Leave BreakAfterJavaFieldAnnotations: false BreakAfterReturnType: None -BreakArrays: true +BreakArrays: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: Always BreakBeforeBraces: Custom @@ -123,14 +123,14 @@ BreakFunctionDefinitionParameters: false BreakInheritanceList: AfterColon BreakStringLiterals: true BreakTemplateDeclarations: Yes -ColumnLimit: 100 -CommentPragmas: '^ IWYU pragma:' +ColumnLimit: 100 +CommentPragmas: "^ IWYU pragma:" CompactNamespaces: false ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 2 Cpp11BracedListStyle: true DerivePointerAlignment: false -DisableFormat: false +DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false @@ -141,22 +141,22 @@ ForEachMacros: - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE -IncludeBlocks: Preserve +IncludeBlocks: Preserve IncludeCategories: - - Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Priority: 2 - SortPriority: 0 - CaseSensitive: false - - Regex: '^(<|"(gtest|gmock|isl|json)/)' - Priority: 3 - SortPriority: 0 - CaseSensitive: false - - Regex: '.*' - Priority: 1 - SortPriority: 0 - CaseSensitive: false -IncludeIsMainRegex: '(Test)?$' -IncludeIsMainSourceRegex: '' + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: ".*" + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "(Test)?$" +IncludeIsMainSourceRegex: "" IndentAccessModifiers: false IndentCaseBlocks: false IndentCaseLabels: false @@ -165,29 +165,29 @@ IndentExternBlock: AfterExternBlock IndentGotoLabels: true IndentPPDirectives: None IndentRequiresClause: true -IndentWidth: 2 +IndentWidth: 2 IndentWrappedFunctionNames: false -InsertBraces: false +InsertBraces: false InsertNewlineAtEOF: false InsertTrailingCommas: None IntegerLiteralSeparator: - Binary: 0 + Binary: 0 BinaryMinDigits: 0 - Decimal: 0 + Decimal: 0 DecimalMinDigits: 0 - Hex: 0 - HexMinDigits: 0 + Hex: 0 + HexMinDigits: 0 JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLines: - AtEndOfFile: false - AtStartOfBlock: true - AtStartOfFile: false -KeepFormFeed: false + AtEndOfFile: false + AtStartOfBlock: true + AtStartOfFile: false +KeepFormFeed: false LambdaBodyIndentation: Signature -LineEnding: DeriveLF -MacroBlockBegin: '' -MacroBlockEnd: '' +LineEnding: DeriveLF +MacroBlockBegin: "" +MacroBlockEnd: "" MainIncludeChar: Quote MaxEmptyLinesToKeep: 1 NamespaceIndentation: All @@ -210,10 +210,10 @@ PenaltyExcessCharacter: 1000000 PenaltyIndentedWhitespace: 0 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left -PPIndentWidth: -1 +PPIndentWidth: -1 QualifierAlignment: Right ReferenceAlignment: Pointer -ReflowComments: false +ReflowComments: false RemoveBracesLLVM: false RemoveEmptyLinesInUnwrappedLines: false RemoveParentheses: Leave @@ -223,7 +223,7 @@ RequiresExpressionIndentation: OuterScope SeparateDefinitionBlocks: Leave ShortNamespaceLines: 1 SkipMacroDefinitionBody: false -SortIncludes: CaseSensitive +SortIncludes: CaseSensitive SortJavaStaticImport: Before SortUsingDeclarations: LexicographicNumeric SpaceAfterCStyleCast: false @@ -242,7 +242,7 @@ SpaceBeforeParensOptions: AfterForeachMacros: true AfterFunctionDefinitionName: false AfterFunctionDeclarationName: false - AfterIfMacros: true + AfterIfMacros: true AfterOverloadedOperator: false AfterPlacementOperator: true AfterRequiresInClause: false @@ -252,28 +252,28 @@ SpaceBeforeRangeBasedForLoopColon: true SpaceBeforeSquareBrackets: false SpaceInEmptyBlock: false SpacesBeforeTrailingComments: 1 -SpacesInAngles: Never +SpacesInAngles: Never SpacesInContainerLiterals: true SpacesInLineCommentPrefix: - Minimum: 1 - Maximum: -1 -SpacesInParens: Never + Minimum: 1 + Maximum: -1 +SpacesInParens: Never SpacesInParensOptions: ExceptDoubleParentheses: false - InCStyleCasts: false + InCStyleCasts: false InConditionalStatements: false InEmptyParentheses: false - Other: false + Other: false SpacesInSquareBrackets: false -Standard: Latest +Standard: Latest StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TableGenBreakInsideDAGArg: DontBreak -TabWidth: 8 -UseTab: Never +TabWidth: 8 +UseTab: Never VerilogBreakBetweenInstancePorts: true WhitespaceSensitiveMacros: - BOOST_PP_STRINGIZE diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 000000000..8f4673f6b --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,2 @@ +MD013: + ignore_code_blocks: true diff --git a/scripts/fix_header_guards.py b/scripts/fix_header_guards.py index fa6b324e9..603fb659c 100755 --- a/scripts/fix_header_guards.py +++ b/scripts/fix_header_guards.py @@ -9,14 +9,21 @@ from pathlib import Path +def normalize_guard_component(component: str) -> str: + """Convert a path component to a non-reserved macro identifier token.""" + normalized = re.sub(r"[^A-Z0-9_]", "_", component.upper()) + normalized = re.sub(r"_+", "_", normalized) + if not normalized or not normalized[0].isalpha(): + return f"FILE_{normalized}" if normalized else "FILE" + return normalized + + def compute_expected_guard(file_path: Path, root: Path) -> str: """Compute expected guard macro: X_Y_HEADER_EXT.""" rel = file_path.relative_to(root) - # X is the first subdirectory, Y is remaining path components - parts = [rel.parts[0].upper().replace("-", "_")] - if len(rel.parts) > 2: - parts.extend(p.upper().replace("-", "_") for p in rel.parts[1:-1]) - parts.extend([rel.stem.upper().replace("-", "_"), rel.suffix[1:].upper()]) + # Include directory components only; a top-level header has none. + parts = [normalize_guard_component(part) for part in rel.parts[:-1]] + parts.extend([normalize_guard_component(rel.stem), normalize_guard_component(rel.suffix[1:])]) return "_".join(parts) @@ -34,16 +41,16 @@ def check_header_guard(file_path: Path, root: Path) -> tuple[bool, str | None]: ifndef_macro = define_macro = endif_macro = None for i in range(min(10, len(lines))): - if m := re.match(r"#ifndef\s+(\w+)\s*$", lines[i]): + if m := re.match(r"#ifndef\s+(\S+)\s*$", lines[i]): ifndef_idx, ifndef_macro = i, m.group(1) - elif m := re.match(r"#define\s+(\w+)\s*$", lines[i]): + elif m := re.match(r"#define\s+(.+?)\s*$", lines[i]): define_idx, define_macro = i, m.group(1) break for i in range(len(lines) - 1, -1, -1): line = lines[i].strip() if line.startswith("#endif"): - if m := re.match(r"#endif\s*//\s*(\w+)\s*$", lines[i]): + if m := re.match(r"#endif\s*//\s*(\S+)\s*$", lines[i]): endif_macro = m.group(1) break @@ -69,9 +76,9 @@ def fix_header_guard(file_path: Path, root: Path) -> bool: ifndef_idx = define_idx = endif_idx = None for i in range(min(10, len(lines))): - if re.match(r"#ifndef\s+\w+\s*$", lines[i]): + if re.match(r"#ifndef\s+\S+\s*$", lines[i]): ifndef_idx = i - elif re.match(r"#define\s+\w+\s*$", lines[i]): + elif re.match(r"#define\s+.+\s*$", lines[i]): define_idx = i break diff --git a/scripts/test/test_fix_header_guards.py b/scripts/test/test_fix_header_guards.py index 94fd54428..f7750fcb3 100644 --- a/scripts/test/test_fix_header_guards.py +++ b/scripts/test/test_fix_header_guards.py @@ -112,6 +112,21 @@ def test_hyphen_in_filename_converted_to_underscore(self, tmp_path: Path) -> Non f = tmp_path / "phlex" / "my-util.hpp" assert M.compute_expected_guard(f, tmp_path) == "PHLEX_MY_UTIL_HPP" + def test_non_identifier_characters_converted_to_underscores(self, tmp_path: Path) -> None: + """Punctuation in path components is replaced with underscores.""" + f = tmp_path / "release.candidate" / "my.geometry.hpp" + assert M.compute_expected_guard(f, tmp_path) == "RELEASE_CANDIDATE_MY_GEOMETRY_HPP" + + def test_repeated_underscores_collapsed(self, tmp_path: Path) -> None: + """Runs of underscores in path components are collapsed.""" + f = tmp_path / "phlex" / "foo__bar.hpp" + assert M.compute_expected_guard(f, tmp_path) == "PHLEX_FOO_BAR_HPP" + + def test_non_letter_prefix_gets_file_prefix(self, tmp_path: Path) -> None: + """Components not beginning with a letter receive a safe FILE_ prefix.""" + f = tmp_path / "1.0-release" / "2nd.geometry.hpp" + assert M.compute_expected_guard(f, tmp_path) == "FILE_1_0_RELEASE_FILE_2ND_GEOMETRY_HPP" + def test_uppercase_components_preserved(self, tmp_path: Path) -> None: """Components are always uppercased regardless of input case.""" f = tmp_path / "Phlex" / "FooBar.hpp" @@ -208,6 +223,25 @@ def test_mismatched_ifndef_and_define(self, tmp_path: Path) -> None: class TestFixHeaderGuard: """Tests for M.fix_header_guard.""" + def test_malformed_top_level_guard_is_fixed(self, tmp_path: Path) -> None: + """A malformed top-level guard is normalized and then validates.""" + content = """\ +#ifndef MY_GEOMETRY.HPP_MY_GEOMETRY_HPP +#define MY_GEOMETRY .HPP_MY_GEOMETRY_HPP +#endif // MY_GEOMETRY.HPP_MY_GEOMETRY_HPP +""" + f = _make_header(tmp_path, ".", "my_geometry.hpp", content) + + valid, _ = M.check_header_guard(f, tmp_path) + assert valid is False + assert M.fix_header_guard(f, tmp_path) is True + + valid, _ = M.check_header_guard(f, tmp_path) + assert valid is True + assert f.read_text(encoding="utf-8") == ( + "#ifndef MY_GEOMETRY_HPP\n#define MY_GEOMETRY_HPP\n#endif // MY_GEOMETRY_HPP\n" + ) + def test_wrong_guard_is_fixed(self, tmp_path: Path) -> None: """A file with a wrong guard is updated to the expected macro.""" f = _make_header(tmp_path, "phlex", "bar.hpp", _wrong_guard_content("PHLEX_BAR_HPP"))