Skip to content

Commit bb249b1

Browse files
authored
chore: add a diagnostic when there is no auth token (#12)
1 parent ad5a20e commit bb249b1

File tree

5 files changed

+132
-2
lines changed

5 files changed

+132
-2
lines changed

internal/lsp/diagnostics.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,42 @@ func tagViolationToDiagnostics(v scanner.TagViolation) []lsp.Diagnostic {
170170
return diags
171171
}
172172

173+
// publishAuthDiagnostic sends a single info diagnostic to the given file
174+
// indicating that the user needs to authenticate.
175+
func (s *Server) publishAuthDiagnostic(uri string) {
176+
if s.client == nil {
177+
return
178+
}
179+
180+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
181+
defer cancel()
182+
183+
s.mu.Lock()
184+
if s.filesWithDiagnostics == nil {
185+
s.filesWithDiagnostics = make(map[string]struct{})
186+
}
187+
s.filesWithDiagnostics[uri] = struct{}{}
188+
s.mu.Unlock()
189+
190+
severity := lsp.SeverityInformation
191+
if err := s.client.PublishDiagnostics(ctx, &lsp.PublishDiagnosticsParams{
192+
URI: lsp.DocumentURI(uri),
193+
Diagnostics: []lsp.Diagnostic{
194+
{
195+
Range: lsp.Range{
196+
Start: lsp.Position{Line: 0, Character: 0},
197+
End: lsp.Position{Line: 0, Character: 0},
198+
},
199+
Severity: &severity,
200+
Source: "infracost",
201+
Message: "Infracost: not authenticated. Run `infracost login` to enable cost analysis and FinOps policies.",
202+
},
203+
},
204+
}); err != nil {
205+
slog.Warn("publishAuthDiagnostic: failed", "uri", uri, "error", err)
206+
}
207+
}
208+
173209
// isLocalFile returns true if the path exists on disk as a regular file.
174210
// This filters out violations from remote modules (e.g. github.com/...) whose
175211
// filenames resolve to non-existent paths.

internal/lsp/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ func (s *Server) DidOpen(_ context.Context, params *lsp.DidOpenTextDocumentParam
263263
if !isSupportedFile(uri) {
264264
return nil
265265
}
266+
267+
if !s.scanner.HasTokenSource() {
268+
s.publishAuthDiagnostic(uri)
269+
}
270+
266271
s.scheduleAnalyze(uri)
267272
return nil
268273
}

internal/scanner/html_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package scanner
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestHtmlToMarkdown(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
in string
13+
want string
14+
}{
15+
{
16+
name: "simple anchor tag",
17+
in: `<a href="https://example.com">link text</a>`,
18+
want: "[link text](https://example.com)",
19+
},
20+
{
21+
name: "anchor with target blank",
22+
in: `<a href="https://example.com" target="_blank">link</a>`,
23+
want: "[link](https://example.com)",
24+
},
25+
{
26+
name: "multiple anchors",
27+
in: `See <a href="https://one.com">first</a> and <a href="https://two.com">second</a>.`,
28+
want: "See [first](https://one.com) and [second](https://two.com).",
29+
},
30+
{
31+
name: "non-anchor HTML tags stripped",
32+
in: `Hello<br>world<div>content</div>`,
33+
want: "Helloworldcontent",
34+
},
35+
{
36+
name: "no HTML passes through unchanged",
37+
in: "plain text with no tags",
38+
want: "plain text with no tags",
39+
},
40+
{
41+
name: "mixed anchors and other HTML",
42+
in: `<b>Bold</b> and <a href="https://example.com">link</a><br>done`,
43+
want: "**Bold** and [link](https://example.com)done",
44+
},
45+
{
46+
name: "Italic tags",
47+
in: `This is <i>italic</i> and <em>emphasized</em>.`,
48+
want: "This is _italic_ and _emphasized_.",
49+
},
50+
}
51+
52+
for _, tt := range tests {
53+
t.Run(tt.name, func(t *testing.T) {
54+
got := htmlToMarkdown(tt.in)
55+
assert.Equal(t, tt.want, got)
56+
})
57+
}
58+
}

internal/scanner/scanner.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,16 +927,29 @@ func envToMap() map[string]string {
927927
return env
928928
}
929929

930+
type regexReplacement struct {
931+
re *regexp.Regexp
932+
rep string
933+
}
934+
930935
var (
931-
reAnchor = regexp.MustCompile(`<a\s+[^>]*href=["']([^"']*)["'][^>]*>(.*?)</a>`)
936+
replaceRegexes = []regexReplacement{
937+
{regexp.MustCompile(`<a\s+[^>]*href=["']([^"']*)["'][^>]*>(.*?)</a>`), "[$2]($1)"},
938+
{regexp.MustCompile(`<b>(.*?)</b>`), "**${1}**"},
939+
{regexp.MustCompile(`<strong>(.*?)</strong>`), "**${1}**"},
940+
{regexp.MustCompile(`<i>(.*?)</i>`), "_${1}_"},
941+
{regexp.MustCompile(`<em>(.*?)</em>`), "_${1}_"},
942+
}
932943
reHTMLTag = regexp.MustCompile(`<[^>]+>`)
933944
)
934945

935946
// htmlToMarkdown converts HTML anchor tags to markdown links and strips
936947
// remaining HTML tags so the output renders cleanly in editors that only
937948
// support markdown (e.g. Zed, Neovim).
938949
func htmlToMarkdown(s string) string {
939-
s = reAnchor.ReplaceAllString(s, "[$2]($1)")
950+
for _, rr := range replaceRegexes {
951+
s = rr.re.ReplaceAllString(s, rr.rep)
952+
}
940953
return reHTMLTag.ReplaceAllString(s, "")
941954
}
942955

internal/scanner/violations_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,24 @@ func TestBuildViolationMarkdown(t *testing.T) {
246246
},
247247
excludes: []string{"Downgrade"},
248248
},
249+
{
250+
name: "HTML in policy detail converted to markdown",
251+
v: FinopsViolation{
252+
PolicyName: "GP3 migration",
253+
PolicySlug: "gp3-migration",
254+
Address: "aws_ebs_volume.data",
255+
Message: `Migrate to <a href="https://docs.aws.amazon.com/ebs/latest/userguide/gp3.html">gp3</a>`,
256+
PolicyDetail: &dashboard.PolicyDetail{
257+
ShortTitle: "Migrate EBS to GP3",
258+
AdditionalDetails: `See <a href="https://infracost.io/docs">docs</a> for more info.<br>Contact support if needed.`,
259+
},
260+
},
261+
contains: []string{
262+
"[gp3](https://docs.aws.amazon.com/ebs/latest/userguide/gp3.html)",
263+
"[docs](https://infracost.io/docs) for more info.",
264+
},
265+
excludes: []string{"<a ", "</a>", "<br>"},
266+
},
249267
}
250268

251269
for _, tt := range tests {

0 commit comments

Comments
 (0)