diff --git a/README.md b/README.md index 91b218c0..a23d12a6 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,16 @@ Set the following configurations in your settings: "projectId": "[Sentry project ID, e.g. "1334031"]", "linePatterns": [ // List of RegExp patterns that match error handling code, e.g. "throw new Error+\\(['\"]([^'\"]+)['\"]\\)", - // !! Make sure to capture the error message in a RegExp group !! + // !! Make sure to capture the error message in a RegExp group and escape special characters to ensure compatibility with JSON Schema!! ] "filters": { [ "repositories": [ - // List of RegExp repo names asociated with this Sentry project + // List of RegExp repo names asociated with this Sentry project. ], "files": [ // List of RegExp that matches file format, e.g. "\\.tsx?", - // or for more specific matching, folder matching, e.g. "(?:web|shared|src)\/.*\\.tsx?" + // or for more specific matching, folder matching, e.g. "(?:web|shared|src)/.*\\.tsx?" ], } } @@ -59,7 +59,7 @@ File patterns can also be narrowed down to certain folders by specifying this in ``` ... -"files": ["(?:web|shared|src)\/.*\\.tsx?"] +"files": ["(?:web|shared|src)/.*\\.tsx?"] ... ``` @@ -78,15 +78,15 @@ File patterns can also be narrowed down to certain folders by specifying this in "projectId": "1334031", "linePatterns": [ "throw new Error+\\(['\"]([^'\"]+)['\"]\\)", - "console\\.(warn|debug|info|error)\\(['\"`]([^'\"`]+)['\"`]\\)" + "console\\.(?:warn|debug|info|error)\\(['\"`]([^'\"`]+)['\"`]\\)" ] "filters": [ { - "repositories": "sourcegraph\/sourcegraph", - "files": ["web\/.*\\.ts?"], + "repositories": "sourcegraph/sourcegraph", + "files": ["web/.*\\.ts?"], }, { - "files": ["sourcegraph-subfolder\/.*\\.tsx?"] + "files": ["sourcegraph-subfolder/.*\\.tsx?"] } ] @@ -111,11 +111,11 @@ Configuration: "linePatterns": ["errors\\.New\\(['\"`](.*)['\"`]\\)"], "filters": [ { - "repositories": ["sourcegraph\/sourcegraph", "sourcegraph\/dev-repo"], - "files": ["/auth\/.*.go?/"], + "repositories": ["sourcegraph/sourcegraph", "sourcegraph/dev-repo"], + "files": ["auth/.*\\.go?"], }, { - "repositories": ["/dev-env/"] + "repositories": ["/dev-env"] } ], ] diff --git a/src/extension.ts b/src/extension.ts index 95f21a5b..f9603624 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,21 +4,13 @@ import * as sourcegraph from 'sourcegraph' import { createDecoration, getParamsFromUriPath, matchSentryProject } from './handler' import { resolveSettings, SentryProject, Settings } from './settings' -/** - * Params derived from the document's URI. - */ -interface Params { - repo: string | null - file: string | null -} - /** * Common error log patterns to use in case no line matching regexes * are set in the sentry extension settings. */ const COMMON_ERRORLOG_PATTERNS = [ // typescript/javascript - /throw new Error+\(['"]([^'"]+)['"]\)/gi, + /throw new ([A-Z][a-z]+)+\(['"]([^'"]+)['"]\)/gi, /console\.(error|info|warn)\(['"`]([^'"`]+)['"`]\)/gi, // go /log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/gi, @@ -84,7 +76,7 @@ export function getDecorations( documentText: string, sentryProjects?: SentryProject[] ): sourcegraph.TextDocumentDecoration[] { - const params: Params = getParamsFromUriPath(documentUri) + const params = getParamsFromUriPath(documentUri) const matched = sentryProjects && matchSentryProject(params, sentryProjects) // Do not decorate lines if the document file format does not match the @@ -189,7 +181,8 @@ function buildUrl(errorQuery: string, sentryProjectId?: string): URL { if (sentryProjectId) { url.searchParams.set('project', sentryProjectId) - url.searchParams.set('query', 'is:unresolved ' + errorQuery) + // Query must be wrapped in double quotes to be used as a search query in Sentry + url.searchParams.set('query', 'is:unresolved ' + '"' + errorQuery + '"') url.searchParams.set('statsPeriod', '14d') } diff --git a/src/handler.ts b/src/handler.ts index c2106017..7d0f7ccc 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -17,15 +17,18 @@ export interface LineDecorationText { * @param textDocumentURI A document URI. * @returns repo and file part of URI. */ -export function getParamsFromUriPath(textDocumentURI: string): Params { - // TODO: Support more than just GitHub & Gitlab. - // TODO: Safeguard for cases where repo/fileMatch are null. - const repoPattern = /(github\.com|gitlab\.com)\/([^\?\#\/]+\/[^\?\#\/]*)/gi +export function getParamsFromUriPath(textDocumentURI: string): Params | null { + let paramsRepo + let fileMatch const filePattern = /#(.*\.(.*))$/gi - const repoMatch = repoPattern.exec(textDocumentURI) - const fileMatch = filePattern.exec(textDocumentURI) + try { + paramsRepo = new URL(textDocumentURI).pathname + fileMatch = filePattern.exec(textDocumentURI) + } catch (err) { + return null + } return { - repo: repoMatch && repoMatch[2], + repo: paramsRepo, file: fileMatch && fileMatch[1], } } @@ -42,8 +45,8 @@ interface Matched { * @param projects Sentry extension projects configurations. * @return Sentry projectID this document reports to. */ -export function matchSentryProject(params: Params, projects: SentryProject[]): Matched | null { - if (!projects || !params.repo || !params.file) { +export function matchSentryProject(params: Params | null, projects: SentryProject[]): Matched | null { + if (!projects || !params || !params.repo || !params.file) { return null } // Check if a Sentry project is associated with this document's repository and/or file and retrieve the project. diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 2d6ab393..e952b464 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -16,21 +16,19 @@ describe('activation', () => { }) }) -const asString = (re: RegExp): string => re.source - const projects: SentryProject[] = [ { name: 'Webapp typescript errors', projectId: '1334031', linePatterns: [ - /throw new Error+\(['"]([^'"]+)['"]\)/, - /console\.(warn|debug|info|error|log)\(['"`]([^'"`]+)['"`]\)/, - /log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/, - ].map(asString), + 'throw new Error+\\([\'"]([^\'"]+)[\'"]\\)', + 'console\\.(?:warn|debug|info|error|log)\\([\'"`]([^\'"`]+)[\'"`]\\)', + 'log\\.(?:Printf|Print|Println)\\([\'"]([^\'"]+)[\'"]\\)', + ], filters: [ { - repositories: [/sourcegraph\/sourcegraph/, /bucket/].map(asString), - files: [/(web|shared|src)\/.*\.tsx?/, /\/.*\\.ts?/].map(asString), + repositories: ['sourcegraph/sourcegraph', '/bucket'], + files: ['(?:web|shared|src)/.*\\.tsx?', '\\.ts?'], }, ], }, @@ -38,11 +36,11 @@ const projects: SentryProject[] = [ { name: 'Dev env errors', projectId: '213332', - linePatterns: [/log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/].map(asString), + linePatterns: ['log\\.(Printf|Print|Println)\\([\'"]([^\'"]+)[\'"]\\)'], filters: [ { - repositories: [/dev-repo/].map(asString), - files: [/(dev)\/.*\\.go?/].map(asString), + repositories: ['/dev-repo'], + files: ['dev/.*\\.go?'], }, ], }, @@ -64,25 +62,25 @@ describe('resolveSettings()', () => { projectId: '1334031', name: 'Webapp typescript errors', linePatterns: [ - /throw new Error+\(['"]([^'"]+)['"]\)/, - /console\.(warn|debug|info|error|log)\(['"`]([^'"`]+)['"`]\)/, - /log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/, - ].map(asString), + 'throw new Error+\\([\'"]([^\'"]+)[\'"]\\)', + 'console\\.(?:warn|debug|info|error|log)\\([\'"`]([^\'"`]+)[\'"`]\\)', + 'log\\.(?:Printf|Print|Println)\\([\'"]([^\'"]+)[\'"]\\)', + ], filters: [ { - repositories: [/sourcegraph\/sourcegraph/, /bucket/].map(asString), - files: [/(web|shared|src)\/.*\.tsx?/, /\/.*\\.ts?/].map(asString), + repositories: ['sourcegraph/sourcegraph', '/bucket'], + files: ['(?:web|shared|src)/.*\\.tsx?', '\\.ts?'], }, ], }, { projectId: '213332', name: 'Dev env errors', - linePatterns: [/log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/].map(asString), + linePatterns: ['log\\.(Printf|Print|Println)\\([\'"]([^\'"]+)[\'"]\\)'], filters: [ { - repositories: [/dev-repo/].map(asString), - files: [/(dev)\/.*\\.go?/].map(asString), + repositories: ['/dev-repo'], + files: ['dev/.*\\.go?'], }, ], }, @@ -108,7 +106,7 @@ const decorateLineInput = [ contentText: ' View logs in Sentry » ', hoverMessage: ' View logs in Sentry » ', linkURL: - 'https://sentry.io/organizations/sourcegraph/issues/?project=134412&query=is%3Aunresolved+cannot+determine+file+path&statsPeriod=14d', + 'https://sentry.io/organizations/sourcegraph/issues/?project=134412&query=is%3Aunresolved+%22cannot+determine+file+path%22&statsPeriod=14d', }, }, }, @@ -127,7 +125,7 @@ const decorateLineInput = [ contentText: ' View logs in Sentry (❕)» ', hoverMessage: ' Add this repository to your Sentry extension settings for project matching.', linkURL: - 'https://sentry.io/organizations/sourcegraph/issues/?project=134412&query=is%3Aunresolved+cannot+determine+file+path&statsPeriod=14d', + 'https://sentry.io/organizations/sourcegraph/issues/?project=134412&query=is%3Aunresolved+%22cannot+determine+file+path%22&statsPeriod=14d', }, }, }, @@ -146,7 +144,7 @@ const decorateLineInput = [ contentText: ' View logs in Sentry (❕)» ', hoverMessage: ' Add this repository to your Sentry extension settings for project matching.', linkURL: - 'https://sentry.io/organizations/sourcegraph/issues/?project=134412&query=is%3Aunresolved+cannot+determine+file+path&statsPeriod=14d', + 'https://sentry.io/organizations/sourcegraph/issues/?project=134412&query=is%3Aunresolved+%22cannot+determine+file+path%22&statsPeriod=14d', }, }, }, @@ -226,7 +224,7 @@ const getDecorationsInput = [ contentText: ' View logs in Sentry » ', hoverMessage: ' View logs in Sentry » ', linkURL: - 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+cannot+determine+file+path&statsPeriod=14d', + 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+%22cannot+determine+file+path%22&statsPeriod=14d', }, }, { @@ -238,7 +236,7 @@ const getDecorationsInput = [ contentText: ' View logs in Sentry » ', hoverMessage: ' View logs in Sentry » ', linkURL: - 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+cannot+determine+delta+info&statsPeriod=14d', + 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+%22cannot+determine+delta+info%22&statsPeriod=14d', }, }, ], @@ -262,7 +260,7 @@ const getDecorationsInput = [ contentText: ' View logs in Sentry » ', hoverMessage: ' View logs in Sentry » ', linkURL: - 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+cannot+determine+file+path&statsPeriod=14d', + 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+%22cannot+determine+file+path%22&statsPeriod=14d', }, }, ], @@ -316,7 +314,7 @@ of(codeView).pipe( contentText: ' View logs in Sentry » ', hoverMessage: ' View logs in Sentry » ', linkURL: - 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+cannot+determine+file+path&statsPeriod=14d', + 'https://sentry.io/organizations/sourcegraph/issues/?project=1334031&query=is%3Aunresolved+%22cannot+determine+file+path%22&statsPeriod=14d', }, }, ], @@ -460,8 +458,8 @@ describe('buildDecorations()', () => { it('should not render anything due to missing code ', () => expect(buildDecorations([], '')).toEqual([])) // set linePatterns back to original state for the other tests projects[0].linePatterns = [ - /throw new Error+\(['"]([^'"]+)['"]\)/, - /console\.(warn|debug|info|error|log)\(['"`]([^'"`]+)['"`]\)/, - /log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/, - ].map(asString) + 'throw new Error+\\([\'"]([^\'"]+)[\'"]\\)', + 'console\\.(?:warn|debug|info|error|log)\\([\'"`]([^\'"`]+)[\'"`]\\)', + 'log\\.(?:Printf|Print|Println)\\([\'"]([^\'"]+)[\'"]\\)', + ] }) diff --git a/src/test/handler.test.ts b/src/test/handler.test.ts index 35a9adee..9cf82b20 100644 --- a/src/test/handler.test.ts +++ b/src/test/handler.test.ts @@ -9,52 +9,50 @@ mock('sourcegraph', sourcegraph) import { createDecoration, findEmptyConfigs, getParamsFromUriPath, matchSentryProject } from '../handler' import { SentryProject } from '../settings' -const asString = (re: RegExp): string => re.source - const projects: SentryProject[] = [ { name: 'Webapp typescript errors', projectId: '1334031', linePatterns: [ - /throw new Error+\(['"]([^'"]+)['"]\)/, - /console\.(warn|debug|info|error|log)\(['"`]([^'"`]+)['"`]\)/, - /log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/, - ].map(asString), + 'throw new Error+\\([\'"]([^\'"]+)[\'"]\\)', + 'console\\.(?:warn|debug|info|error|log)\\([\'"`]([^\'"`]+)[\'"`]\\)', + 'log\\.(Printf|Print|Println)\\([\'"]([^\'"]+)[\'"]\\)', + ], filters: [ { - repositories: [/sourcegraph\/sourcegraph/, /bucket/].map(asString), - files: [/(web|shared|src)\/.*\.tsx?/, /\/.*\\.ts?/].map(asString), + repositories: ['sourcegraph/sourcegraph', '/bucket'], + files: ['(?:web|shared|src)/.*\\.tsx?', '\\.ts?'], }, ], }, { name: 'Dev env errors', projectId: '213332', - linePatterns: [/log\.(Printf|Print|Println)\(['"]([^'"]+)['"]\)/].map(asString), + linePatterns: ['log\\.(?:Printf|Print|Println)\\([\'"]([^\'"]+)[\'"]\\)'], filters: [ { - repositories: [/dev-repo/].map(asString), - files: [/dev\/.*.go?/].map(asString), + repositories: ['/dev-repo'], + files: ['dev/.*\\.go?'], }, ], }, { name: 'docs pages errors', projectId: '544533', - linePatterns: [/throw new Error+\(['"]([^'"]+)['"]\)/].map(asString), + linePatterns: ['throw new Error+\\([\'"]([^\'"]+)[\'"]\\)'], filters: [ { - repositories: [/sourcegraph\/docs/].map(asString), + repositories: ['sourcegraph/docs'], }, ], }, { name: 'dot com errors', projectId: '242677', - linePatterns: [/throw new Error+\(['"]([^'"]+)['"]\)/].map(asString), + linePatterns: ['throw new Error+\\([\'"]([^\'"]+)[\'"]\\)'], filters: [ { - files: [/\.tsx?/].map(asString), + files: ['\\.tsx?'], }, ], }, @@ -66,10 +64,9 @@ const setDefaults = async () => { } describe('getParamsFromUriPath', () => { - beforeEach(setDefaults) it('extracts repo and file params from root folder', () => expect(getParamsFromUriPath('git://github.com/sourcegraph/sourcegraph?264...#index.tsx')).toEqual({ - repo: 'sourcegraph/sourcegraph', + repo: '/sourcegraph/sourcegraph', file: 'index.tsx', })) @@ -77,76 +74,69 @@ describe('getParamsFromUriPath', () => { expect( getParamsFromUriPath('git://github.com/sourcegraph/sourcegraph?264...#web/src/e2e/index.e2e.test.tsx') ).toEqual({ - repo: 'sourcegraph/sourcegraph', + repo: '/sourcegraph/sourcegraph', file: 'web/src/e2e/index.e2e.test.tsx', })) - it('return empty repo if host is not GitHub', () => - expect(getParamsFromUriPath('git://unknownhost.com/sourcegraph/testrepo#http/req/main.go')).toEqual({ - repo: null, - file: 'http/req/main.go', + it('returns null if URI is corrupted', () => + expect(getParamsFromUriPath('git://thisisnotavaliduri')).toEqual({ + repo: '', + file: null, })) - it('return empty file if document has no file format', () => - expect(getParamsFromUriPath('git://github.com/sourcegraph/sourcegraph/testrepo#formatless')).toEqual({ - repo: 'sourcegraph/sourcegraph', + it('returns empty file if document has no file format', () => + expect(getParamsFromUriPath('git://github.com/sourcegraph/testrepo#formatless')).toEqual({ + repo: '/sourcegraph/testrepo', file: null, })) }) const paramsInput = [ { - goal: 'returns a web project that matches the repo and file patterns', + goal: 'returns a web project that matches the repo and file patterns and an empty missingConfigs list', params: { - repo: 'sourcegraph/sourcegraph', + repo: '/sourcegraph/sourcegraph', file: 'web/src/storm/index.tsx', }, expected: { project: projects[0], missingConfigs: [] }, }, { - goal: 'returns a dev project that matches the repo and file patterns', + goal: 'returns a dev project that matches the repo and file patterns and an empty missingConfigs list', params: { - repo: 'sourcegraph/dev-repo', + repo: '/sourcegraph/dev-repo', file: 'dev/backend/main.go', }, expected: { project: projects[1], missingConfigs: [] }, }, { - goal: 'returns file false for not matching file patterns', - params: { - repo: 'sourcegraph/dev-repo', - file: 'dev/test/start.rb', - }, - expected: null, - }, - { - goal: 'returns undefined for not matching repo and false for not matching file patterns', + goal: 'returns null for not matching file patterns', params: { - repo: 'sourcegraph/test-repo', + repo: '/sourcegraph/dev-repo', file: 'dev/test/start.rb', }, expected: null, }, { - goal: 'returns undefined for not matching repo and file patterns', + goal: 'returns null for not matching repo and file patterns', params: { - repo: 'sourcegraph/test-repo', + repo: '/sourcegraph/test-repo', file: 'dev/test/start.rb', }, expected: null, }, { - goal: 'returns project for matching repo and undefined for not having file patterns', + goal: 'returns project for matching repo despite not having a files config and an empty missingConfigs list', params: { - repo: 'sourcegraph/docs', + repo: '/sourcegraph/docs', file: 'src/development/tutorial.tsx', }, expected: { project: projects[2], missingConfigs: [] }, }, { - goal: 'returns project for matching file patterns', + goal: + 'returns project for matching file patterns despite not having a repositories config and an empty missingConfigs list', params: { - repo: 'sourcegraph/website', + repo: '/sourcegraph/website', file: 'web/search/start.tsx', }, expected: { project: projects[3], missingConfigs: [] }, @@ -166,11 +156,11 @@ const incompleteConfigs: { goal: string; settings: SentryProject; expected: stri settings: { name: 'sourcegraph', projectId: '1334031', - linePatterns: [/logger\.debug\(['"`]([^'"`]+)['"`]\);/].map(asString), + linePatterns: ['logger\\.debug\\([\'"`]([^\'"`]+)[\'"`]\\);'], filters: [ { repositories: undefined, - files: [/(web|shared|src).*\.java?/, /(dev|src).*\.java?/, /.java?/].map(asString), + files: ['(?:web|shared|src)/.*\\.java?', '(?:dev|src)/.*\\.java?', '\\.java?'], }, ], }, @@ -181,11 +171,11 @@ const incompleteConfigs: { goal: string; settings: SentryProject; expected: stri settings: { name: 'sourcegraph', projectId: '', - linePatterns: [/logger\.debug\(['"`]([^'"`]+)['"`]\);/].map(asString), + linePatterns: ['logger\\.debug\\([\'"`]([^\'"`]+)[\'"`]\\);'], filters: [ { repositories: undefined, - files: [/(web|shared|src).*\.java?/, /(dev|src).*\.java?/, /.java?/].map(asString), + files: ['(?:web|shared|src)/.*\\.java?', '(?:dev|src)/.*\\.java?', '\\.java?'], }, ], },