Skip to content

Commit ffbfb36

Browse files
paulirwinclaude
andauthored
Add Lucene1001 / Lucene1002 analyzers for TokenStream contract, apache#1146 (apache#1288)
* Add Lucene1001 / Lucene1002 analyzers for TokenStream contract (apache#1146) Adds two new public Roslyn analyzers (C# + VB) that surface the TokenStream subclassing rules at compile time as warnings instead of runtime exceptions: - Lucene1001: an override of End()/Reset()/Close() on a TokenStream subclass must call the corresponding base method. - Lucene1002: a non-abstract Tokenizer subclass must override End() (passes if any class in the hierarchy between the subclass and Tokenizer already provides the override). Both analyzers ship with code fix providers. The primary audience is consumers of the library who write their own TokenStream-derived types — Lucene.NET's in-tree TokenStream subclasses intentionally match the upstream Java behavior, which itself does not always call super.X() in End/Reset/Close. Each in-tree violation is therefore suppressed with [SuppressMessage] and a "LUCENENET specific" comment that explains why it matches the Java parent. Two End() overrides (PrefixAwareTokenFilter, PrefixAndSuffixAwareTokenFilter) carry an additional "LUCENENET TODO" because the upstream Java omission of super.end() may be a latent bug worth revisiting. The test verifier helper now references System.Runtime so analyzer fixes that introduce base calls returning void don't trigger a spurious CS0012 in the post-fix compilation check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address Copilot review feedback on Lucene1001/Lucene1002 analyzers - Lucene1001 CS code fix: emit ThrowStatement (not ExpressionStatement) when converting an expression-bodied `=> throw ...` member, so the produced code is valid C#. - Lucene1001 CS + VB analyzers: exclude lambdas, anonymous methods, and (CS) local functions from the base-call scan. A base call inside an uninvoked delegate body should not satisfy the contract. - Lucene1002 CS + VB code fixes: use ElasticCarriageReturnLineFeed instead of hardcoded line endings so the formatter normalizes to the document's convention. - DiagnosticVerifier: harden System.Runtime.dll lookup with a TRUSTED_PLATFORM_ASSEMBLIES fallback and a clear error if neither resolves. - Tests: add VerifyCSharpFix/VerifyBasicFix coverage for Lucene1002, a VerifyCSharpFix for the Lucene1001 expression-bodied case plus a new throw-expression regression, lambda/local-function exclusion tests for Lucene1001 (CS + VB), and the missing VB indirect-Tokenizer-inheritance test for Lucene1002. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 03c5fa6 commit ffbfb36

25 files changed

Lines changed: 2110 additions & 0 deletions

src/Lucene.Net.Analysis.Common/Analysis/Miscellaneous/PrefixAndSuffixAwareTokenFilter.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
// Lucene version compatibility level 4.8.1
2+
using System.Diagnostics.CodeAnalysis;
3+
24
namespace Lucene.Net.Analysis.Miscellaneous
35
{
46
/*
@@ -85,16 +87,29 @@ public sealed override bool IncrementToken()
8587
return suffix.IncrementToken();
8688
}
8789

90+
// LUCENENET specific: matches the upstream Java implementation, which does not call super.reset().
91+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
92+
Justification = "Matches upstream Lucene Java behavior; TokenStream.Reset() is a no-op so the omission is equivalent.")]
8893
public override void Reset()
8994
{
9095
suffix.Reset();
9196
}
9297

98+
// LUCENENET specific: matches the upstream Java implementation, which does not call super.close().
99+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
100+
Justification = "Matches upstream Lucene Java behavior; TokenStream.Close() is a no-op so the omission is equivalent.")]
93101
public override void Close()
94102
{
95103
suffix.Close();
96104
}
97105

106+
// LUCENENET TODO: the upstream Java does not call super.end() here, so neither do we — but
107+
// TokenStream.End() does ClearAttributes() and resets PositionIncrementAttribute, which the
108+
// documented contract says End() overrides should do. This may be a latent bug in upstream
109+
// Lucene that the .NET port should investigate; see Lucene.NET issue tracker if reproducing
110+
// a problem with end-of-stream attribute state on this filter.
111+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
112+
Justification = "Matches upstream Lucene Java behavior; see LUCENENET TODO above for why this may need to be revisited.")]
98113
public override void End()
99114
{
100115
suffix.End();

src/Lucene.Net.Analysis.Common/Analysis/Miscellaneous/PrefixAwareTokenFilter.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Lucene version compatibility level 4.8.1
22
using Lucene.Net.Analysis.TokenAttributes;
33
using Lucene.Net.Util;
4+
using System.Diagnostics.CodeAnalysis;
45

56
namespace Lucene.Net.Analysis.Miscellaneous
67
{
@@ -169,12 +170,22 @@ public virtual Token UpdateSuffixToken(Token suffixToken, Token lastPrefixToken)
169170
return suffixToken;
170171
}
171172

173+
// LUCENENET TODO: the upstream Java does not call super.end() here, so neither do we — but
174+
// TokenStream.End() does ClearAttributes() and resets PositionIncrementAttribute, which the
175+
// documented contract says End() overrides should do. This may be a latent bug in upstream
176+
// Lucene that the .NET port should investigate; see Lucene.NET issue tracker if reproducing
177+
// a problem with end-of-stream attribute state on this filter.
178+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
179+
Justification = "Matches upstream Lucene Java behavior; see LUCENENET TODO above for why this may need to be revisited.")]
172180
public override void End()
173181
{
174182
prefix.End();
175183
suffix.End();
176184
}
177185

186+
// LUCENENET specific: matches the upstream Java implementation, which does not call super.close().
187+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
188+
Justification = "Matches upstream Lucene Java behavior; TokenStream.Close() is a no-op so the omission is equivalent.")]
178189
public override void Close()
179190
{
180191
prefix.Close();

src/Lucene.Net.Analysis.Common/Analysis/Miscellaneous/SingleTokenTokenStream.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Lucene.Net.Analysis.TokenAttributes;
33
using Lucene.Net.Diagnostics;
44
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
56

67
namespace Lucene.Net.Analysis.Miscellaneous
78
{
@@ -58,6 +59,10 @@ public override sealed bool IncrementToken()
5859
}
5960
}
6061

62+
// LUCENENET specific: matches the upstream Java implementation, which does not call super.reset().
63+
// TokenStream.Reset() has an empty body so this is equivalent.
64+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
65+
Justification = "Matches upstream Lucene Java behavior; TokenStream.Reset() is a no-op so the omission is equivalent.")]
6166
public override void Reset()
6267
{
6368
exhausted = false;

src/Lucene.Net.Analysis.Common/Analysis/Sinks/TeeSinkTokenFilter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Lucene.Net.Util;
33
using System;
44
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
56
using JCG = J2N.Collections.Generic;
67

78
namespace Lucene.Net.Analysis.Sinks
@@ -248,6 +249,8 @@ public override sealed bool IncrementToken()
248249
return true;
249250
}
250251

252+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
253+
Justification = "Sink streams replay cached state; calling base.End() would clear the attributes that RestoreState then sets. Matches Lucene Java behavior.")]
251254
public override sealed void End()
252255
{
253256
if (finalState != null)
@@ -256,6 +259,8 @@ public override sealed void End()
256259
}
257260
}
258261

262+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
263+
Justification = "Sink streams replay cached state; the iterator reset is sufficient and base.Reset() (no-op on TokenStream) provides nothing further. Matches Lucene Java behavior.")]
259264
public override sealed void Reset()
260265
{
261266
it = cachedStates.GetEnumerator();

src/Lucene.Net.Analysis.Common/Analysis/Synonym/SlowSynonymFilter.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Lucene.Net.Util;
44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
67
using JCG = J2N.Collections.Generic;
78

89
namespace Lucene.Net.Analysis.Synonym
@@ -309,6 +310,11 @@ private void Copy(AttributeSource target, AttributeSource source)
309310
}
310311
}
311312

313+
// LUCENENET specific: matches the upstream Java implementation, which calls input.reset()
314+
// directly rather than super.reset(). Behavior is identical (TokenFilter.Reset() just chains
315+
// to m_input.Reset()), but we keep the call shape to preserve Java parity.
316+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
317+
Justification = "Matches upstream Lucene Java behavior; TokenFilter.Reset() chains to m_input.Reset() so this is equivalent.")]
312318
public override void Reset()
313319
{
314320
m_input.Reset();

src/Lucene.Net.Analysis.Phonetic/DoubleMetaphoneFilter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Lucene.Net.Support;
66
using System;
77
using System.Collections.Generic;
8+
using System.Diagnostics.CodeAnalysis;
89

910
namespace Lucene.Net.Analysis.Phonetic
1011
{
@@ -118,6 +119,10 @@ public override bool IncrementToken()
118119
}
119120
}
120121

122+
// LUCENENET specific: matches the upstream Java implementation, which calls input.reset()
123+
// directly rather than super.reset(). Behavior is identical.
124+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
125+
Justification = "Matches upstream Lucene Java behavior; TokenFilter.Reset() chains to m_input.Reset() so this is equivalent.")]
121126
public override void Reset()
122127
{
123128
m_input.Reset();

src/Lucene.Net.Analysis.Phonetic/PhoneticFilter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Lucene.Net.Analysis.TokenAttributes;
44
using Lucene.Net.Analysis.TokenAttributes.Extensions;
55
using System;
6+
using System.Diagnostics.CodeAnalysis;
67

78
namespace Lucene.Net.Analysis.Phonetic
89
{
@@ -104,6 +105,10 @@ public override bool IncrementToken()
104105
return true;
105106
}
106107

108+
// LUCENENET specific: matches the upstream Java implementation, which calls input.reset()
109+
// directly rather than super.reset(). Behavior is identical.
110+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
111+
Justification = "Matches upstream Lucene Java behavior; TokenFilter.Reset() chains to m_input.Reset() so this is equivalent.")]
107112
public override void Reset()
108113
{
109114
m_input.Reset();

src/Lucene.Net.Highlighter/Highlight/TokenStreamFromTermPositionVector.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Lucene.Net.Index;
55
using Lucene.Net.Util;
66
using System.Collections.Generic;
7+
using System.Diagnostics.CodeAnalysis;
78
using JCG = J2N.Collections.Generic;
89

910
namespace Lucene.Net.Search.Highlight
@@ -126,6 +127,9 @@ public override bool IncrementToken()
126127
return false;
127128
}
128129

130+
// LUCENENET specific: matches the upstream Java implementation, which does not call super.reset().
131+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
132+
Justification = "Matches upstream Lucene Java behavior; TokenStream.Reset() is a no-op so the omission is equivalent.")]
129133
public override void Reset()
130134
{
131135
this.tokensAtCurrentPosition = this.positionedTokens.GetEnumerator();

src/Lucene.Net/Analysis/CachingTokenFilter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Diagnostics.CodeAnalysis;
23
using JCG = J2N.Collections.Generic;
34

45
namespace Lucene.Net.Analysis
@@ -67,6 +68,8 @@ public override bool IncrementToken()
6768
return true;
6869
}
6970

71+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
72+
Justification = "Caching filter intentionally restores the previously captured final state instead of chaining to base.End(); matches Lucene Java behavior.")]
7073
public override void End()
7174
{
7275
if (finalState != null)
@@ -82,6 +85,8 @@ public override void End()
8285
/// the first time. You should <see cref="Reset()"/> the inner tokenstream before wrapping
8386
/// it with <see cref="CachingTokenFilter"/>.
8487
/// </summary>
88+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
89+
Justification = "Caching filter intentionally does not call base.Reset() (which would propagate to the wrapped input); matches Lucene Java behavior. See the doc comment.")]
8590
public override void Reset()
8691
{
8792
if (cache != null)

src/Lucene.Net/Analysis/NumericTokenStream.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Lucene.Net.Diagnostics;
33
using Lucene.Net.Util;
44
using System;
5+
using System.Diagnostics.CodeAnalysis;
56

67
namespace Lucene.Net.Analysis
78
{
@@ -330,6 +331,9 @@ public NumericTokenStream SetSingleValue(float value)
330331
return this;
331332
}
332333

334+
// LUCENENET specific: matches the upstream Java implementation, which does not call super.reset().
335+
[SuppressMessage("Design", "Lucene1001:TokenStream override of End()/Reset()/Close() must call the corresponding base method.",
336+
Justification = "Matches upstream Lucene Java behavior; TokenStream.Reset() is a no-op so the omission is equivalent.")]
333337
public override void Reset()
334338
{
335339
if (valSize == 0)

0 commit comments

Comments
 (0)