diff --git a/server/api.go b/server/api.go index abded33f..43121dcf 100644 --- a/server/api.go +++ b/server/api.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "regexp" + "sort" "strings" "time" @@ -184,6 +185,14 @@ func (s *server) doSearch(ctx context.Context, backend *Backend, q *pb.Query) (* }) } + // Derank test file results by sorting them to the end + sort.SliceStable(reply.Results, func(i, j int) bool { + return !isTestFile(reply.Results[i].Path) && isTestFile(reply.Results[j].Path) + }) + sort.SliceStable(reply.FileResults, func(i, j int) bool { + return !isTestFile(reply.FileResults[i].Path) && isTestFile(reply.FileResults[j].Path) + }) + reply.Info = &api.Stats{ RE2Time: search.Stats.Re2Time, GitTime: search.Stats.GitTime, @@ -275,3 +284,19 @@ func (s *server) ServeAPISearch(ctx context.Context, w http.ResponseWriter, r *h replyJSON(ctx, w, 200, reply) } + +func isTestFile(path string) bool { + return strings.HasPrefix(path, "test/") || + strings.HasPrefix(path, "tests/") || + strings.Contains(path, "/test/") || + strings.Contains(path, "/tests/") || + strings.Contains(path, "/__tests__/") || + strings.HasSuffix(path, "_test.go") || + strings.HasSuffix(path, ".test.ts") || + strings.HasSuffix(path, ".test.js") || + strings.HasSuffix(path, ".test.tsx") || + strings.HasSuffix(path, "_test.py") || + strings.HasSuffix(path, ".spec.ts") || + strings.HasSuffix(path, ".spec.js") || + strings.HasSuffix(path, ".spec.tsx") +} diff --git a/web/src/codesearch/highlight.js b/web/src/codesearch/highlight.js index 88b4a217..7a13b620 100644 --- a/web/src/codesearch/highlight.js +++ b/web/src/codesearch/highlight.js @@ -74,18 +74,20 @@ function flattenTokens(tokens) { for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (typeof token === 'string') { - result.push({type: null, text: token}); + result.push({type: null, alias: null, text: token}); } else if (token.content) { + var alias = token.alias || null; if (typeof token.content === 'string') { - result.push({type: token.type, text: token.content}); + result.push({type: token.type, alias: alias, text: token.content}); } else { - // Nested tokens - flatten and inherit type for untyped children + // Nested tokens - flatten and inherit type/alias for untyped children var nested = flattenTokens( Array.isArray(token.content) ? token.content : [token.content] ); for (var j = 0; j < nested.length; j++) { result.push({ type: nested[j].type || token.type, + alias: nested[j].alias || alias, text: nested[j].text }); } @@ -124,12 +126,12 @@ function splitAtBounds(segments, bounds) { var lastCut = 0; for (var c = 0; c < cuts.length; c++) { if (cuts[c] > lastCut) { - result.push({type: seg.type, text: seg.text.substring(lastCut, cuts[c])}); + result.push({type: seg.type, alias: seg.alias, text: seg.text.substring(lastCut, cuts[c])}); } lastCut = cuts[c]; } if (lastCut < seg.text.length) { - result.push({type: seg.type, text: seg.text.substring(lastCut)}); + result.push({type: seg.type, alias: seg.alias, text: seg.text.substring(lastCut)}); } } @@ -149,14 +151,22 @@ function buildNodes(segments, matchStart, matchEnd) { var segEnd = pos + seg.text.length; var inMatch = (pos >= matchStart && segEnd <= matchEnd); - if (!seg.type && !inMatch) { + var hasType = seg.type || seg.alias; + if (!hasType && !inMatch) { nodes.push(document.createTextNode(seg.text)); } else { var span = document.createElement('span'); var classes = []; - if (seg.type) { + if (hasType) { classes.push('token'); - classes.push(seg.type); + if (seg.type) classes.push(seg.type); + if (seg.alias) { + if (Array.isArray(seg.alias)) { + for (var a = 0; a < seg.alias.length; a++) classes.push(seg.alias[a]); + } else { + classes.push(seg.alias); + } + } } if (inMatch) { classes.push('matchstr');