Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5c1cb81
Initial plan
Copilot Sep 8, 2025
1713662
Add basic XML doc comment position warning - initial implementation
Copilot Sep 8, 2025
8698eef
Add warning for XML documentation comments not positioned as first no…
Copilot Sep 8, 2025
21333cf
Fix XML doc comment position check to analyze actual line content ins…
Copilot Sep 8, 2025
7d2be7e
Address feedback by disabling hardcoded column limit approach tempora…
Copilot Sep 8, 2025
6413c55
Merge branch 'main' into copilot/fix-920e1d4e-0c13-4c25-8d7b-183dbc29…
T-Gro Sep 18, 2025
92f2c02
Address feedback: remove hardcoded column limit, disable warning temp…
Copilot Sep 19, 2025
e12e8cc
Implement XML doc comment position check in LexFilter instead of lexe…
Copilot Sep 19, 2025
fb42cf8
Merge branch 'main' into copilot/fix-920e1d4e-0c13-4c25-8d7b-183dbc29…
T-Gro Sep 22, 2025
28a6204
Fix type constraint mismatch in FSComp.SR.xmlDocNotFirstOnLine() usage
Copilot Sep 22, 2025
56607de
Fix unused variable warning in LexFilter pattern match
Copilot Sep 22, 2025
9d9a664
Merge branch 'main' into copilot/fix-920e1d4e-0c13-4c25-8d7b-183dbc29…
T-Gro Oct 17, 2025
83a3454
Update xlf localization files for xmlDocNotFirstOnLine entry
Copilot Nov 30, 2025
bd22c0e
Add lexer-level tracking for XML doc comment position warning
Copilot Dec 2, 2025
3070e3d
Fix incorrect /// comment usage in infos.fs - should be //
Copilot Dec 2, 2025
1f45f17
Disable XML doc position warning - implementation too aggressive
Copilot Dec 3, 2025
4ac9a26
Re-implement XML doc comment position warning using token position tr…
Copilot Dec 3, 2025
a560672
Add token position tracking to more token types and start updating tests
Copilot Dec 3, 2025
3f936f2
Update XmlDocTests to expect FS3879 warning for misplaced /// comments
Copilot Dec 3, 2025
df81dfb
Fix remaining test failures: update neg45.bsl baseline and type membe…
Copilot Dec 3, 2025
fc51e55
Fix code formatting in LexHelpers.fs and LexHelpers.fsi
Copilot Dec 3, 2025
4b58239
Merge branch 'main' into copilot/fix-920e1d4e-0c13-4c25-8d7b-183dbc29…
abonie Dec 5, 2025
c24643d
Update neg45.bsl baseline with all three FS3879 warnings
Copilot Dec 5, 2025
c2736dc
Fix /// comment in Trimming/Program.fs that was causing build failures
Copilot Dec 5, 2025
e171853
Change triple slash to regular comments
abonie Dec 8, 2025
b4a85cb
Fix XML documentation comment parse errors
abonie Dec 8, 2025
b3984a0
Undo changes to neg45.bsl
abonie Dec 8, 2025
0c6e957
Suppress FS3879 warning in AOT trimming test projects
Copilot Dec 9, 2025
d4b6661
Add release notes entry for FS3879 warning
Copilot Dec 9, 2025
c2112c5
Add test for DU cases with /// after case definition
Copilot Dec 9, 2025
2faae14
Fix DU test - use correct lambda signature (fun _ -> ()) instead of (…
Copilot Dec 10, 2025
4d16c5b
Fix DU test expected column positions - CaseB line should be Col 12, …
Copilot Dec 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* Add FSharpCodeCompletionOptions ([PR #19030](https://github.com/dotnet/fsharp/pull/19030))
* Type checker: recover on checking binding parameter constraints ([#19046](https://github.com/dotnet/fsharp/pull/19046))
* Debugger: provide breakpoint ranges for short lambdas ([#19067](https://github.com/dotnet/fsharp/pull/19067))
* Add warning FS3879 for XML documentation comments not positioned as first non-whitespace on line. ([PR #18891](https://github.com/dotnet/fsharp/pull/18891))

### Changed

Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Checking/infos.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1516,7 +1516,7 @@ type ILFieldInfo =
match x with
| ILFieldInfo(tinfo, _) -> tinfo.TypeInstOfRawMetadata
#if !NO_TYPEPROVIDERS
| ProvidedField _ -> [] /// GENERIC TYPE PROVIDERS
| ProvidedField _ -> [] // GENERIC TYPE PROVIDERS
#endif

/// Get the name of the field
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1813,4 +1813,5 @@ featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type an
3876,lexWarnDirectivesMustMatch,"There is another %s for this warning already in line %d."
3877,lexLineDirectiveMappingIsNotUnique,"The file '%s' was also pointed to in a line directive in '%s'. Proper warn directive application may not be possible."
3878,tcAttributeIsNotValidForUnionCaseWithFields,"This attribute is not valid for use on union cases with fields."
3879,xmlDocNotFirstOnLine,"XML documentation comments should be the first non-whitespace text on a line."
featureReturnFromFinal,"Support for ReturnFromFinal/YieldFromFinal in computation expressions to enable tailcall optimization when available on the builder."
13 changes: 13 additions & 0 deletions src/Compiler/SyntaxTree/LexHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ type LexArgs =
mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus
mutable stringNest: LexerInterpolatedStringNesting
mutable interpolationDelimiterLength: int
/// Tracks the line number of the last non-whitespace, non-comment token
mutable lastTokenEndLine: int
/// Tracks the end column of the last non-whitespace, non-comment token
mutable lastTokenEndColumn: int
}

/// possible results of lexing a long Unicode escape sequence in a string literal, e.g. "\U0001F47D",
Expand All @@ -84,6 +88,8 @@ let mkLexargs
stringNest = []
pathMap = pathMap
interpolationDelimiterLength = 0
lastTokenEndLine = 0
lastTokenEndColumn = 0
}

/// Register the lexbuf and call the given function
Expand Down Expand Up @@ -444,9 +450,16 @@ module Keywords =
if IsCompilerGeneratedName s then
warning (Error(FSComp.SR.lexhlpIdentifiersContainingAtSymbolReserved (), lexbuf.LexemeRange))

// Track token position for XML doc comment checking
args.lastTokenEndLine <- lexbuf.EndPos.Line
args.lastTokenEndColumn <- lexbuf.EndPos.Column
args.resourceManager.InternIdentifierToken s

let KeywordOrIdentifierToken args (lexbuf: Lexbuf) s =
// Track token position for XML doc comment checking
args.lastTokenEndLine <- lexbuf.EndPos.Line
args.lastTokenEndColumn <- lexbuf.EndPos.Column

match keywordTable.TryGetValue s with
| true, v ->
match v with
Expand Down
24 changes: 15 additions & 9 deletions src/Compiler/SyntaxTree/LexHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,21 @@ type LexResourceManager =

/// The context applicable to all lexing functions (tokens, strings etc.)
type LexArgs =
{ conditionalDefines: string list
resourceManager: LexResourceManager
diagnosticsLogger: DiagnosticsLogger
applyLineDirectives: bool
pathMap: PathMap
mutable ifdefStack: LexerIfdefStack
mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus
mutable stringNest: LexerInterpolatedStringNesting
mutable interpolationDelimiterLength: int }
{
conditionalDefines: string list
resourceManager: LexResourceManager
diagnosticsLogger: DiagnosticsLogger
applyLineDirectives: bool
pathMap: PathMap
mutable ifdefStack: LexerIfdefStack
mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus
mutable stringNest: LexerInterpolatedStringNesting
mutable interpolationDelimiterLength: int
/// Tracks the line number of the last non-whitespace, non-comment token
mutable lastTokenEndLine: int
/// Tracks the end column of the last non-whitespace, non-comment token
mutable lastTokenEndColumn: int
}

type LongUnicodeLexResult =
| SurrogatePair of uint16 * uint16
Expand Down
30 changes: 23 additions & 7 deletions src/Compiler/lex.fsl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ let fail args (lexbuf:UnicodeLexing.Lexbuf) msg dflt =
args.diagnosticsLogger.ErrorR(Error(msg,m))
dflt

/// Update tracking of the last token position for XML doc comment position checking
let updateLastTokenPos (args: LexArgs) (lexbuf: UnicodeLexing.Lexbuf) =
let endPos = lexbuf.EndPos
args.lastTokenEndLine <- endPos.Line
args.lastTokenEndColumn <- endPos.Column

//--------------------------
// Integer parsing

Expand Down Expand Up @@ -412,6 +418,7 @@ rule token (args: LexArgs) (skip: bool) = parse
| int
{ let s = removeUnderscores (lexeme lexbuf)
// Allow <max_int+1> to parse as min_int. Allowed only because we parse '-' as an operator.
updateLastTokenPos args lexbuf
if Ranges.isInt32BadMax s then INT32(Int32.MinValue, true (* 'true' = 'bad'*) ) else
let n =
try int32 s with _ -> fail args lexbuf (FSComp.SR.lexOutsideThirtyTwoBitSigned()) 0
Expand All @@ -422,6 +429,7 @@ rule token (args: LexArgs) (skip: bool) = parse
| int32
{ let s = removeUnderscores (lexemeTrimRight lexbuf 1)
// Allow <max_int+1> to parse as min_int. Allowed only because we parse '-' as an operator.
updateLastTokenPos args lexbuf
if Ranges.isInt32BadMax s then INT32(Int32.MinValue, true (* 'true' = 'bad'*) ) else
let n =
try int32 s with _ -> fail args lexbuf (FSComp.SR.lexOutsideThirtyTwoBitSigned()) 0
Expand Down Expand Up @@ -484,10 +492,12 @@ rule token (args: LexArgs) (skip: bool) = parse
}

| ieee64
{ IEEE64 (try float(lexeme lexbuf) with _ -> fail args lexbuf (FSComp.SR.lexInvalidFloat()) 0.0) }
{ updateLastTokenPos args lexbuf
IEEE64 (try float(lexeme lexbuf) with _ -> fail args lexbuf (FSComp.SR.lexInvalidFloat()) 0.0) }

| decimal
{ try
{ updateLastTokenPos args lexbuf
try
let s = removeUnderscores (lexemeTrimRight lexbuf 1)
// This implements a range check for decimal literals
let d = System.Decimal.Parse(s,System.Globalization.NumberStyles.AllowExponent ||| System.Globalization.NumberStyles.Number,System.Globalization.CultureInfo.InvariantCulture)
Expand Down Expand Up @@ -740,6 +750,10 @@ rule token (args: LexArgs) (skip: bool) = parse
| "///" op_char*
{ // Match exactly 3 slash, 4+ slash caught by preceding rule
let m = lexbuf.LexemeRange
// Check if there was a token on this line before this /// comment
// If lastTokenEndLine matches the current line, there's code before the comment
if args.lastTokenEndLine = m.StartLine then
warning (Error(FSComp.SR.xmlDocNotFirstOnLine(), m))
let doc = lexemeTrimLeft lexbuf 3
let sb = (new StringBuilder(100)).Append(doc)
if not skip then LINE_COMMENT (LexCont.SingleLineComment(args.ifdefStack, args.stringNest, 1, m))
Expand Down Expand Up @@ -852,7 +866,7 @@ rule token (args: LexArgs) (skip: bool) = parse

| '(' { LPAREN }

| ')' { RPAREN }
| ')' { updateLastTokenPos args lexbuf; RPAREN }

| '*' { STAR }

Expand Down Expand Up @@ -910,13 +924,13 @@ rule token (args: LexArgs) (skip: bool) = parse

| "[<" { LBRACK_LESS }

| "]" { RBRACK }
| "]" { updateLastTokenPos args lexbuf; RBRACK }

| "|]" { BAR_RBRACK }
| "|]" { updateLastTokenPos args lexbuf; BAR_RBRACK }

| "|}" { BAR_RBRACE }
| "|}" { updateLastTokenPos args lexbuf; BAR_RBRACE }

| ">]" { GREATER_RBRACK }
| ">]" { updateLastTokenPos args lexbuf; GREATER_RBRACK }

| "{"
{
Expand Down Expand Up @@ -966,10 +980,12 @@ rule token (args: LexArgs) (skip: bool) = parse
// will be reported w.r.t. the first '{'
args.stringNest <- (counter - 1, style, d, altR, m) :: rest
let cont = LexCont.Token(args.ifdefStack, args.stringNest)
updateLastTokenPos args lexbuf
RBRACE cont

| _ ->
let cont = LexCont.Token(args.ifdefStack, args.stringNest)
updateLastTokenPos args lexbuf
RBRACE cont
}

Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFrameworks>net9.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>3879</NoWarn>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion tests/AheadOfTime/Trimming/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8262,7 +8262,7 @@ let func7000()=
test "test7935" (lazy(sprintf "%+0G" -10.0M)) "-10"
test "test7936" (lazy(sprintf "%+05G" -10.0M)) "-0010"// "00-10"
test "test7937" (lazy(sprintf "%+01G" -10.0M)) "-10"
test "test7938" (lazy(sprintf "%+0*G" 7 -10.0M)) "-000010"/// "0000-10"
test "test7938" (lazy(sprintf "%+0*G" 7 -10.0M)) "-000010"// "0000-10"
test "test7939" (lazy(sprintf "%+0.5G" -10.0M)) "-10"
test "test7940" (lazy(sprintf "%+0.*G" 4 -10.0M)) "-10"
test "test7941" (lazy(sprintf "%+0*.*G" 5 4 -10.0M)) "-0010"// "00-10"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFrameworks>net9.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>3879</NoWarn>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFrameworks>net9.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>3879</NoWarn>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<OtherFlags>--standalone</OtherFlags>
</PropertyGroup>
Expand Down
Loading
Loading