Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
},
{
"id": "codeOwnership.file",
"command": "open",
"commandArguments": [
"${get(context, `codeOwnership.file.${resource.uri}.url`)}"
],
"actionItem": {
"label": "${get(context, `codeOwnership.file.${resource.uri}.label`)}",
"description": "${get(context, `codeOwnership.file.${resource.uri}.description`)}"
Expand Down
6 changes: 4 additions & 2 deletions src/codeOwners.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const SHOW_SINGLE_OWNER_MAX_LENGTH = 14

export function formatCodeOwners(owners: string[] | null): { label: string | null; description: string | null } {
if (owners === null) {
export function formatCodeOwners(
owners: string[] | null | undefined
): { label: string | null; description: string | null } {
if (!owners) {
return { label: null, description: null }
}
if (owners.length === 0) {
Expand Down
44 changes: 19 additions & 25 deletions src/codeownersFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const getCodeownersFile = memoizeAsync(
}: {
uri: string
sourcegraph: typeof import('sourcegraph')
}): Promise<string | null> => {
}): Promise<{ path: string; content: string } | null> => {
const { repo, rev } = resolveURI(uri)
const { data } = await sourcegraph.commands.executeCommand(
'queryGraphQL',
Expand All @@ -35,10 +35,10 @@ const getCodeownersFile = memoizeAsync(
throw new Error('repository or commit not found when getting CODEOWNERS file')
}
if (data.repository.commit.codeownersBlob && data.repository.commit.codeownersBlob.content) {
return data.repository.commit.codeownersBlob.content
return { path: 'CODEOWNERS', content: data.repository.commit.codeownersBlob.content }
}
if (data.repository.commit.githubCodeownersBlob && data.repository.commit.githubCodeownersBlob.content) {
return data.repository.commit.githubCodeownersBlob.content
return { path: '.github/CODEOWNERS', content: data.repository.commit.githubCodeownersBlob.content }
}
return null
},
Expand All @@ -48,15 +48,21 @@ const getCodeownersFile = memoizeAsync(
}
)

export async function getCodeOwners(uri: string): Promise<string[] | null> {
export interface ResolvedOwnersLine {
path: string
lineNumber: number
owners: string[]
}

export async function getCodeOwners(uri: string): Promise<ResolvedOwnersLine | null> {
const codeownersFile = await getCodeownersFile({ uri, sourcegraph })
if (!codeownersFile) {
return null
}
const codeownersEntries = parseCodeownersFile(codeownersFile)
const entry = matchCodeownersFile(resolveURI(uri).path, codeownersEntries)
if (entry) {
return entry.owners
const codeownersEntries = parseCodeownersFile(codeownersFile.content)
const matching = codeownersEntries.find(entry => matchPattern(resolveURI(uri).path, entry.pattern))
if (matching) {
return { path: codeownersFile.path, lineNumber: matching.lineNumber, owners: matching.owners }
}
return null
}
Expand All @@ -65,6 +71,7 @@ export async function getCodeOwners(uri: string): Promise<string[] | null> {
* An individual entry from a CODEOWNERS file
*/
export interface CodeOwnersEntry {
lineNumber: number
pattern: string
owners: string[]
}
Expand All @@ -74,17 +81,17 @@ export interface CodeOwnersEntry {
* of the file).
*/
export function parseCodeownersFile(str: string): CodeOwnersEntry[] {
const entries = []
const entries: CodeOwnersEntry[] = []
const lines = str.split('\n')

for (const line of lines) {
const [content] = line.split('#')
for (const [index, lineText] of lines.entries()) {
const [content] = lineText.split('#')
const trimmed = content.trim()
if (trimmed === '') {
continue
}
const [pattern, ...owners] = trimmed.split(/\s+/)
entries.push({ pattern, owners })
entries.push({ pattern, owners, lineNumber: index + 1 })
}

return entries.reverse()
Expand All @@ -96,16 +103,3 @@ export function parseCodeownersFile(str: string): CodeOwnersEntry[] {
export function matchPattern(filename: string, pattern: string): boolean {
return ignore().add(pattern).ignores(filename)
}

/**
* Match a filename against CODEOWNERS entries to determine which (if any) it
* matches.
*/
export function matchCodeownersFile(filename: string, entries: CodeOwnersEntry[]): CodeOwnersEntry | null {
for (const entry of entries) {
if (matchPattern(filename, entry.pattern)) {
return entry
}
}
return null
}
17 changes: 13 additions & 4 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { startWith, switchMap, filter, map } from 'rxjs/operators'
import * as sourcegraph from 'sourcegraph'
import { getCodeOwners } from './codeownersFile'
import { formatCodeOwners } from './codeOwners'
import { resolveURI } from './uri'

export function activate(ctx: sourcegraph.ExtensionContext): void {
ctx.subscriptions.add(
Expand All @@ -22,17 +23,25 @@ export function activate(ctx: sourcegraph.ExtensionContext): void {
switchMap(async textDocument => {
if (!sourcegraph.configuration.get().value['codeOwnership.hide']) {
try {
return { textDocument, owners: await getCodeOwners(textDocument.uri) }
return { textDocument, resolvedOwnersLine: await getCodeOwners(textDocument.uri) }
} catch (err) {
console.error(`Error getting code owners for ${textDocument.uri}:`, err)
}
}
return { textDocument, owners: null }
return { textDocument, resolvedOwnersLine: null }
})
)
.subscribe(({ textDocument, owners }) => {
const { label, description } = formatCodeOwners(owners)
.subscribe(({ textDocument, resolvedOwnersLine }) => {
const { label, description } = formatCodeOwners(resolvedOwnersLine?.owners)
const { repo, rev } = resolveURI(textDocument.uri)
const url =
resolvedOwnersLine &&
new URL(
`${repo}@${rev}/-/blob/${resolvedOwnersLine.path}#L${resolvedOwnersLine.lineNumber}`,
sourcegraph.internal.sourcegraphURL
).href
sourcegraph.internal.updateContext({
[`codeOwnership.file.${textDocument.uri}.url`]: url,
[`codeOwnership.file.${textDocument.uri}.label`]: label,
[`codeOwnership.file.${textDocument.uri}.description`]: description,
})
Expand Down
6 changes: 3 additions & 3 deletions src/uri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ export function resolveURI(uri: string): { repo: string; rev: string; path: stri
const url = new URL(uri)
if (url.protocol === 'git:') {
return {
repo: (url.host + url.pathname).replace(/^\/*/, '').toLowerCase(),
rev: url.search.slice(1).toLowerCase(),
path: url.hash.slice(1),
repo: decodeURIComponent((url.host + url.pathname).replace(/^\/*/, '').toLowerCase()),
rev: decodeURIComponent(url.search.slice(1).toLowerCase()),
path: decodeURIComponent(url.hash.slice(1)),
}
}
throw new Error(`unrecognized URI: ${JSON.stringify(uri)} (supported URI schemes: git)`)
Expand Down