diff --git a/src/FSharpLint.Core/AssemblyInfo.fs b/src/FSharpLint.Core/AssemblyInfo.fs index 464cf1ab6..9d30a1f17 100644 --- a/src/FSharpLint.Core/AssemblyInfo.fs +++ b/src/FSharpLint.Core/AssemblyInfo.fs @@ -3,5 +3,6 @@ module FSharpLint.Core.AssemblyInfo open System.Runtime.CompilerServices [] +[] () \ No newline at end of file diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs index d2735ec43..9aa5d7656 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs @@ -1,6 +1,7 @@ module FSharpLint.Rules.Helper.SourceLength open System +open System.Text open System.Text.RegularExpressions open FSharpLint.Framework open FSharpLint.Framework.Suggestion @@ -34,31 +35,38 @@ let rec private getTopLevelBalancedPairs (toProcess: List (beginIndex, index) :: getTopLevelBalancedPairs tail List.Empty | _::restOfStack -> getTopLevelBalancedPairs tail restOfStack +let internal stripMultilineComments (source: string) = + let markers = + multilineCommentMarkerRegex.Matches source + |> Seq.map (fun markerMatch -> + let index = markerMatch.Index + if source.[index] = '(' then + Begin index + else + End index) + |> Seq.sortBy (function | Begin index -> index | End index -> index) + |> Seq.toList + + // Process block comment removal + // - If no comments, return input as is + // - If one comment, just remove it directly + // - If several comments, remove them all starting from the last, as removing them from the front changes the offsets of later ones + match getTopLevelBalancedPairs markers List.Empty with + | [] -> source + | [ (startIndex, endIndex) ] -> source.Remove(startIndex, (endIndex + multilineCommentMarkerRegexCaptureGroupLength) - startIndex ) + | pairs -> + + (pairs, StringBuilder(source)) + ||> List.foldBack + (fun (startIndex, endIndex)(currSource: StringBuilder) -> + currSource.Remove(startIndex, (endIndex + multilineCommentMarkerRegexCaptureGroupLength) - startIndex)) + |> _.ToString() + let checkSourceLengthRule (config:Config) range fileContents errorName (skipRanges: array) = let error name lineCount actual = let errorFormatString = Resources.GetString("RulesSourceLengthError") String.Format(errorFormatString, name, lineCount, actual) - let stripMultilineComments (source: string) = - let markers = - multilineCommentMarkerRegex.Matches source - |> Seq.map (fun markerMatch -> - let index = markerMatch.Index - if source.[index] = '(' then - Begin index - else - End index) - |> Seq.sortBy (function | Begin index -> index | End index -> index) - |> Seq.toList - - getTopLevelBalancedPairs markers List.Empty - |> List.fold - (fun (currSource: string) (startIndex, endIndex) -> - let left = currSource.AsSpan(0, startIndex) - let right = currSource.AsSpan(endIndex + multilineCommentMarkerRegexCaptureGroupLength) - String.Concat(left, right)) - source - match tryFindTextOfRange range fileContents with | Some(sourceCode) -> let sourceCode = diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs index cd960deec..d87aac2dc 100644 --- a/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs @@ -75,6 +75,23 @@ let dog x = ()""") Assert.IsFalse this.ErrorsExist + [] + member this.FunctionTooManyLinesWithMultiLineCommentAtEnd() = + this.Parse($""" +module Program + +let dog x = + (* + Foo + Bar + *) + %s{generateNewLines (FunctionLength - 4) 4} + (* + Baz + *) + ()""") + Assert.IsFalse this.ErrorsExist + [] member this.FunctionTooManyLinesWithNestsedMultiLineComment() = this.Parse($""" @@ -354,7 +371,20 @@ let UnionLength = 500 [] type TestMaxLinesInUnion() = inherit TestAstNodeRuleBase.TestAstNodeRuleBase(MaxLinesInUnion.rule { Config.MaxLines = UnionLength }) - // TODO: Add tests. + + // Test a Union type with an acceptable number of lines, with inline block comments + // This cased used to trip the exception described in https://github.com/fsprojects/FSharpLint/issues/869 + // but should be fixed now. + [] + member this.UnionNotTooManyLines() = + this.Parse """ +/// Represents a single group of bindings in a class with an implicit constructor +type IncrClassBindingGroup = + | IncrClassBindingGroup of Tast.Binding list * (*isStatic:*) bool* (*recursive:*) bool + | IncrClassDo of Expr * (*isStatic:*) bool +""" + + Assert.IsFalse(this.ErrorExistsAt(4, 5)) [] let RecordLength = 500 @@ -414,3 +444,75 @@ module Program let foo = "" exception SomeException of string""") Assert.IsFalse(this.ErrorExistsAt(2, 0)) + +// Tests for 'stripMultilineComments' +// ref https://github.com/fsprojects/FSharpLint/issues/869 +[] +type TestStripMultilineComments() = + + [] + member this.RemoveCommentFromStart() = + let input = """ +let dog x = + (* + Foo + Bar + *) + printf System.String.Empty + ()""" + + let expected = """ +let dog x = + + printf System.String.Empty + ()""" + + let actual = FSharpLint.Rules.Helper.SourceLength.stripMultilineComments input + Assert.AreEqual(expected, actual) + + [] + member this.RemoveCommentFromEnd() = + let input = """ +let dog x = + printf System.String.Empty + (* + Baz + *) + ()""" + + let expected = """ +let dog x = + printf System.String.Empty + + ()""" + + let actual = FSharpLint.Rules.Helper.SourceLength.stripMultilineComments input + Assert.AreEqual(expected, actual) + + [] + member this.RemoveMultipleComments() = + let input = """ +let dog x = + (* + Foo (* baz *) + let (*) = id + Bar + *) + let (*) a b = a + b + printf System.String.Empty + (* + Baz + *) + ()""" + + let expected = """ +let dog x = + + let (*) a b = a + b + printf System.String.Empty + + ()""" + + let actual = FSharpLint.Rules.Helper.SourceLength.stripMultilineComments input + Assert.AreEqual(expected, actual) +