mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 18:11:48 +00:00
Update dependency @sourcegraph/eslint-config to ^0.19.1 (#11225)
Co-authored-by: Renovate Bot <bot@renovateapp.com> Co-authored-by: Felix Becker <felix.b@outlook.com>
This commit is contained in:
parent
0211f65837
commit
f425fda6e0
@ -2,7 +2,7 @@ import initStoryshots from '@storybook/addon-storyshots'
|
||||
import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer'
|
||||
import * as path from 'path'
|
||||
import { pathToFileURL } from 'url'
|
||||
import { recordCoverage } from '../shared/src/e2e/coverage'
|
||||
import { recordCoverage } from '../shared/src/testing/coverage'
|
||||
|
||||
// This test suite does not actually test anything.
|
||||
// It just loads up the storybook in Puppeteer and records its coverage,
|
||||
|
||||
@ -3,7 +3,7 @@ module.exports = {
|
||||
extends: '../.eslintrc.js',
|
||||
parserOptions: {
|
||||
...baseConfig.parserOptions,
|
||||
project: [__dirname + '/tsconfig.json', __dirname + '/src/e2e/tsconfig.json'],
|
||||
project: [__dirname + '/tsconfig.json', __dirname + '/src/end-to-end/tsconfig.json'],
|
||||
},
|
||||
overrides: baseConfig.overrides,
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ It works as follows:
|
||||
Code shared between multiple code hosts.
|
||||
- `config/`
|
||||
Configuration code that is bundled via webpack. The configuration code adds properties to `window` that make it easier to tell what environment the script is running in. This is useful because the code can be run in the content script, background, options page, or in the actual page when injected by Phabricator and each environment will have different ways to do different things.
|
||||
- `e2e/`
|
||||
- `end-to-end/`
|
||||
E2E test suite.
|
||||
- `scripts/`
|
||||
Build scripts.
|
||||
@ -130,16 +130,16 @@ Click reload for Sourcegraph at `about:debugging`
|
||||
|
||||
### e2e tests
|
||||
|
||||
The test suite in e2e/github.test.ts runs on the release branch `bext/release` in both Chrome and Firefox against a Sourcegraph Docker instance.
|
||||
The test suite in `end-to-end/github.test.ts` runs on the release branch `bext/release` in both Chrome and Firefox against a Sourcegraph Docker instance.
|
||||
|
||||
The test suite in e2e/phabricator.test.ts tests the Phabricator native integration.
|
||||
The test suite in end-to-end/phabricator.test.ts tests the Phabricator native integration.
|
||||
It assumes an existing Sourcegraph and Phabricator instance that has the Phabricator extension installed.
|
||||
There are automated scripts to set up the Phabricator instance, see https://docs.sourcegraph.com/dev/phabricator_gitolite.
|
||||
It currently does not run in CI and is intended to be run manually for release testing.
|
||||
|
||||
e2e/bitbucket.test.ts tests the browser extension on a Bitbucket Server instance.
|
||||
`end-to-end/bitbucket.test.ts` tests the browser extension on a Bitbucket Server instance.
|
||||
|
||||
e2e/gitlab.test.ts tests the browser extension on gitlab.com (or a private Gitlab instance).
|
||||
`end-to-end/gitlab.test.ts` tests the browser extension on gitlab.com (or a private Gitlab instance).
|
||||
|
||||
## Deploy
|
||||
|
||||
|
||||
@ -9,14 +9,18 @@ const contentEntry = '../../src/config/content.entry.js'
|
||||
const backgroundEntry = '../../src/config/background.entry.js'
|
||||
const optionsEntry = '../../src/config/options.entry.js'
|
||||
const pageEntry = '../../src/config/page.entry.js'
|
||||
const extEntry = '../../src/config/extension.entry.js'
|
||||
const extensionEntry = '../../src/config/extension.entry.js'
|
||||
|
||||
const config: webpack.Configuration = {
|
||||
entry: {
|
||||
// Browser extension
|
||||
background: buildEntry(extEntry, backgroundEntry, '../../src/browser-extension/scripts/backgroundPage.main.ts'),
|
||||
options: buildEntry(extEntry, optionsEntry, '../../src/browser-extension/scripts/optionsPage.main.tsx'),
|
||||
inject: buildEntry(extEntry, contentEntry, '../../src/browser-extension/scripts/contentPage.main.ts'),
|
||||
background: buildEntry(
|
||||
extensionEntry,
|
||||
backgroundEntry,
|
||||
'../../src/browser-extension/scripts/backgroundPage.main.ts'
|
||||
),
|
||||
options: buildEntry(extensionEntry, optionsEntry, '../../src/browser-extension/scripts/optionsPage.main.tsx'),
|
||||
inject: buildEntry(extensionEntry, contentEntry, '../../src/browser-extension/scripts/contentPage.main.ts'),
|
||||
|
||||
// Common native integration entry point (Gitlab, Bitbucket)
|
||||
integration: buildEntry(pageEntry, '../../src/native-integration/integration.main.ts'),
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"yarn": ">1.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 TS_NODE_COMPILER_OPTIONS=\"{\\\"module\\\":\\\"commonjs\\\"}\" node -r ts-node/register scripts/dev",
|
||||
"dev": "NODE_ENV=development NODE_OPTIONS=--max_old_space_size=4096 TS_NODE_COMPILER_OPTIONS=\"{\\\"module\\\":\\\"commonjs\\\"}\" node -r ts-node/register scripts/development",
|
||||
"dev:no-reload": "AUTO_RELOAD=false yarn run dev",
|
||||
"dev:firefox": "if type web-ext 2>/dev/null; then web-ext run --source-dir ./build/firefox; else echo 'web-ext not found. Install it with: yarn global add web-ext'; exit 1; fi",
|
||||
"build": "NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 TS_NODE_COMPILER_OPTIONS=\"{\\\"module\\\":\\\"commonjs\\\"}\" node -r ts-node/register scripts/build",
|
||||
@ -17,8 +17,8 @@
|
||||
"eslint": "eslint --cache '**/*.ts?(x)'",
|
||||
"stylelint": "stylelint 'src/**/*.scss'",
|
||||
"clean": "rm -rf build/ dist/ *.zip *.xpi .checksum",
|
||||
"test": "jest --testPathIgnorePatterns e2e",
|
||||
"test-e2e": "mocha './src/e2e/**/*.test.ts'",
|
||||
"test": "jest --testPathIgnorePatterns end-to-end",
|
||||
"test-e2e": "mocha './src/end-to-end/**/*.test.ts'",
|
||||
"bundlesize": "GITHUB_TOKEN= bundlesize"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import signale from 'signale'
|
||||
import io from 'socket.io'
|
||||
import socketIo from 'socket.io'
|
||||
|
||||
/**
|
||||
* Returns a trigger function that notifies the extension to reload itself.
|
||||
@ -9,7 +9,7 @@ export const initializeServer = (): (() => void) => {
|
||||
logger.config({ displayTimestamp: true })
|
||||
|
||||
// Since this port is hard-coded, it must match background.ts
|
||||
const socketIOServer = io.listen(8890)
|
||||
const socketIOServer = socketIo.listen(8890)
|
||||
logger.await('Ready for a browser extension to connect')
|
||||
socketIOServer.on('connect', () => {
|
||||
logger.info('Browser extension connected')
|
||||
|
||||
@ -34,6 +34,6 @@ function addVersionsToManifest(links: string[]): void {
|
||||
fs.writeFileSync(updatesManifestPath, JSON.stringify(updatesManifest, null, 2), 'utf8')
|
||||
}
|
||||
|
||||
const links = process.argv.slice(2).filter(l => !l.match(/latest.xpi$/) && !l.match(/updates.json$/))
|
||||
const links = process.argv.slice(2).filter(link => !link.match(/latest.xpi$/) && !link.match(/updates.json$/))
|
||||
|
||||
addVersionsToManifest(links)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import signale from 'signale'
|
||||
import webpack from 'webpack'
|
||||
import config from '../config/webpack/prod.config'
|
||||
import config from '../config/webpack/production.config'
|
||||
import * as tasks from './tasks'
|
||||
|
||||
const buildChrome = tasks.buildChrome('prod')
|
||||
@ -12,7 +12,7 @@ const compiler = webpack(config)
|
||||
|
||||
signale.await('Webpack compilation')
|
||||
|
||||
compiler.run((err, stats) => {
|
||||
compiler.run((error, stats) => {
|
||||
console.log(stats.toString(tasks.WEBPACK_STATS_OPTIONS))
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { noop } from 'lodash'
|
||||
import signale from 'signale'
|
||||
import webpack from 'webpack'
|
||||
import config from '../config/webpack/dev.config'
|
||||
import config from '../config/webpack/development.config'
|
||||
import * as autoReloading from './auto-reloading'
|
||||
import * as tasks from './tasks'
|
||||
|
||||
@ -24,10 +24,10 @@ compiler.watch(
|
||||
{
|
||||
aggregateTimeout: 300,
|
||||
},
|
||||
(err, stats) => {
|
||||
(error, stats) => {
|
||||
signale.complete(stats.toString(tasks.WEBPACK_STATS_OPTIONS))
|
||||
|
||||
if (err || stats.hasErrors()) {
|
||||
if (error || stats.hasErrors()) {
|
||||
signale.error('Webpack compilation error')
|
||||
return
|
||||
}
|
||||
@ -40,25 +40,25 @@ function ensurePaths(): void {
|
||||
|
||||
export function copyAssets(): void {
|
||||
signale.await('Copy assets')
|
||||
const dir = 'build/dist'
|
||||
shelljs.rm('-rf', dir)
|
||||
shelljs.mkdir('-p', dir)
|
||||
shelljs.cp('-R', 'assets/*', dir)
|
||||
shelljs.cp('-R', 'src/browser-extension/pages/*', dir)
|
||||
const directory = 'build/dist'
|
||||
shelljs.rm('-rf', directory)
|
||||
shelljs.mkdir('-p', directory)
|
||||
shelljs.cp('-R', 'assets/*', directory)
|
||||
shelljs.cp('-R', 'src/browser-extension/pages/*', directory)
|
||||
signale.success('Assets copied')
|
||||
}
|
||||
|
||||
function copyExtensionAssets(toDir: string): void {
|
||||
shelljs.mkdir('-p', `${toDir}/js`, `${toDir}/css`, `${toDir}/img`)
|
||||
shelljs.cp('build/dist/js/background.bundle.js', `${toDir}/js`)
|
||||
shelljs.cp('build/dist/js/inject.bundle.js', `${toDir}/js`)
|
||||
shelljs.cp('build/dist/js/options.bundle.js', `${toDir}/js`)
|
||||
shelljs.cp('build/dist/css/style.bundle.css', `${toDir}/css`)
|
||||
shelljs.cp('build/dist/css/options-style.bundle.css', `${toDir}/css`)
|
||||
shelljs.cp('build/dist/css/options-style.bundle.css', `${toDir}/css`)
|
||||
shelljs.cp('-R', 'build/dist/img/*', `${toDir}/img`)
|
||||
shelljs.cp('build/dist/background.html', toDir)
|
||||
shelljs.cp('build/dist/options.html', toDir)
|
||||
function copyExtensionAssets(toDirectory: string): void {
|
||||
shelljs.mkdir('-p', `${toDirectory}/js`, `${toDirectory}/css`, `${toDirectory}/img`)
|
||||
shelljs.cp('build/dist/js/background.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/js/inject.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/js/options.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/css/style.bundle.css', `${toDirectory}/css`)
|
||||
shelljs.cp('build/dist/css/options-style.bundle.css', `${toDirectory}/css`)
|
||||
shelljs.cp('build/dist/css/options-style.bundle.css', `${toDirectory}/css`)
|
||||
shelljs.cp('-R', 'build/dist/img/*', `${toDirectory}/img`)
|
||||
shelljs.cp('build/dist/background.html', toDirectory)
|
||||
shelljs.cp('build/dist/options.html', toDirectory)
|
||||
}
|
||||
|
||||
export function copyIntegrationAssets(): void {
|
||||
@ -89,16 +89,16 @@ const BROWSER_BLACKLIST = {
|
||||
firefox: ['key'] as const,
|
||||
}
|
||||
|
||||
function writeSchema(env: BuildEnv, browser: Browser, writeDir: string): void {
|
||||
fs.writeFileSync(`${writeDir}/schema.json`, JSON.stringify(schema, null, 4))
|
||||
function writeSchema(environment: BuildEnv, browser: Browser, writeDirectory: string): void {
|
||||
fs.writeFileSync(`${writeDirectory}/schema.json`, JSON.stringify(schema, null, 4))
|
||||
}
|
||||
|
||||
const version = utcVersion()
|
||||
|
||||
function writeManifest(env: BuildEnv, browser: Browser, writeDir: string): void {
|
||||
function writeManifest(environment: BuildEnv, browser: Browser, writeDirectory: string): void {
|
||||
const manifest = {
|
||||
...omit(extensionInfo, ['dev', 'prod', ...BROWSER_BLACKLIST[browser]]),
|
||||
...omit(extensionInfo[env], BROWSER_BLACKLIST[browser]),
|
||||
...omit(extensionInfo[environment], BROWSER_BLACKLIST[browser]),
|
||||
}
|
||||
|
||||
if (EXTENSION_PERMISSIONS_ALL_URLS) {
|
||||
@ -113,22 +113,22 @@ function writeManifest(env: BuildEnv, browser: Browser, writeDir: string): void
|
||||
|
||||
delete manifest.$schema
|
||||
|
||||
if (env === 'prod') {
|
||||
if (environment === 'prod') {
|
||||
manifest.version = version
|
||||
}
|
||||
|
||||
fs.writeFileSync(`${writeDir}/manifest.json`, JSON.stringify(manifest, null, 4))
|
||||
fs.writeFileSync(`${writeDirectory}/manifest.json`, JSON.stringify(manifest, null, 4))
|
||||
}
|
||||
|
||||
function buildForBrowser(browser: Browser): (env: BuildEnv) => () => void {
|
||||
ensurePaths()
|
||||
return env => {
|
||||
return environment => {
|
||||
const title = BROWSER_TITLES[browser]
|
||||
|
||||
const buildDir = path.resolve(process.cwd(), `${BUILDS_DIR}/${browser}`)
|
||||
const buildDirectory = path.resolve(process.cwd(), `${BUILDS_DIR}/${browser}`)
|
||||
|
||||
writeManifest(env, browser, buildDir)
|
||||
writeSchema(env, browser, buildDir)
|
||||
writeManifest(environment, browser, buildDirectory)
|
||||
writeSchema(environment, browser, buildDirectory)
|
||||
|
||||
return () => {
|
||||
// Allow only building for specific browser targets.
|
||||
@ -137,17 +137,17 @@ function buildForBrowser(browser: Browser): (env: BuildEnv) => () => void {
|
||||
return
|
||||
}
|
||||
|
||||
signale.await(`Building the ${title} ${env} bundle`)
|
||||
signale.await(`Building the ${title} ${environment} bundle`)
|
||||
|
||||
copyExtensionAssets(buildDir)
|
||||
copyExtensionAssets(buildDirectory)
|
||||
|
||||
const zipDest = path.resolve(process.cwd(), `${BUILDS_DIR}/bundles/${BROWSER_BUNDLE_ZIPS[browser]}`)
|
||||
if (zipDest) {
|
||||
const zipDestination = path.resolve(process.cwd(), `${BUILDS_DIR}/bundles/${BROWSER_BUNDLE_ZIPS[browser]}`)
|
||||
if (zipDestination) {
|
||||
shelljs.mkdir('-p', `./${BUILDS_DIR}/bundles`)
|
||||
shelljs.exec(`cd ${buildDir} && zip -q -r ${zipDest} *`)
|
||||
shelljs.exec(`cd ${buildDirectory} && zip -q -r ${zipDestination} *`)
|
||||
}
|
||||
|
||||
signale.success(`Done building the ${title} ${env} bundle`)
|
||||
signale.success(`Done building the ${title} ${environment} bundle`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
export function assertEnv(env: typeof window['EXTENSION_ENV']): void {
|
||||
if (window.EXTENSION_ENV !== env) {
|
||||
export function assertEnvironment(environment: typeof window['EXTENSION_ENV']): void {
|
||||
if (window.EXTENSION_ENV !== environment) {
|
||||
throw new Error(
|
||||
'Detected transitive import of an entrypoint! ' +
|
||||
window.EXTENSION_ENV +
|
||||
' attempted to import a file that is only intended to be imported by ' +
|
||||
env +
|
||||
environment +
|
||||
'.'
|
||||
)
|
||||
}
|
||||
@ -65,13 +65,13 @@ describe('OptionsContainer', () => {
|
||||
const buildRenderer = (): ((ui: React.ReactElement) => void) => {
|
||||
let rerender: RenderResult['rerender'] | undefined
|
||||
|
||||
return ui => {
|
||||
return element => {
|
||||
if (rerender) {
|
||||
rerender(ui)
|
||||
rerender(element)
|
||||
} else {
|
||||
const renderedRes = render(ui)
|
||||
const renderedResult = render(element)
|
||||
|
||||
rerender = renderedRes.rerender
|
||||
rerender = renderedResult.rerender
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,28 +72,28 @@ export class OptionsContainer extends React.Component<OptionsContainerProps, Opt
|
||||
this.setState({ status: 'connecting', connectionError: undefined })
|
||||
return this.props.ensureValidSite(url).pipe(
|
||||
map(() => url),
|
||||
catchError(err => of(asError(err)))
|
||||
catchError(error => of(asError(error)))
|
||||
)
|
||||
}),
|
||||
catchError(err => of(asError(err))),
|
||||
catchError(error => of(asError(error))),
|
||||
share()
|
||||
)
|
||||
|
||||
this.subscriptions.add(
|
||||
fetchingSite.subscribe(async res => {
|
||||
fetchingSite.subscribe(async result => {
|
||||
let url = ''
|
||||
|
||||
if (isErrorLike(res)) {
|
||||
if (isErrorLike(result)) {
|
||||
this.setState({
|
||||
status: 'error',
|
||||
connectionError: isHTTPAuthError(res)
|
||||
connectionError: isHTTPAuthError(result)
|
||||
? ConnectionErrors.AuthError
|
||||
: ConnectionErrors.UnableToConnect,
|
||||
})
|
||||
url = this.state.sourcegraphURL
|
||||
} else {
|
||||
this.setState({ status: 'connected' })
|
||||
url = res
|
||||
url = result
|
||||
}
|
||||
|
||||
const urlHasPermissions = await props.hasPermissions(url)
|
||||
|
||||
@ -101,7 +101,7 @@ describe('ServerUrlForm', () => {
|
||||
a: 'https://different.com',
|
||||
}
|
||||
|
||||
const submitObs = cold('a', urls).pipe(
|
||||
const submitObservable = cold('a', urls).pipe(
|
||||
switchMap(url => {
|
||||
const emit = of(undefined).pipe(
|
||||
tap(() => {
|
||||
@ -114,7 +114,7 @@ describe('ServerUrlForm', () => {
|
||||
})
|
||||
)
|
||||
|
||||
expectObservable(submitObs).toBe('5s a', { a: undefined })
|
||||
expectObservable(submitObservable).toBe('5s a', { a: undefined })
|
||||
})
|
||||
})
|
||||
|
||||
@ -148,7 +148,7 @@ describe('ServerUrlForm', () => {
|
||||
a: 'https://different.com',
|
||||
}
|
||||
|
||||
const submitObs = cold('a', urls).pipe(
|
||||
const submitObservable = cold('a', urls).pipe(
|
||||
switchMap(url => {
|
||||
const emit = of(undefined).pipe(
|
||||
tap(() => {
|
||||
@ -161,7 +161,7 @@ describe('ServerUrlForm', () => {
|
||||
})
|
||||
)
|
||||
|
||||
expectObservable(submitObs).toBe('a', { a: undefined })
|
||||
expectObservable(submitObservable).toBe('a', { a: undefined })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import '../../shared/polyfills'
|
||||
|
||||
import io from 'socket.io-client'
|
||||
import socketIoClient from 'socket.io-client'
|
||||
|
||||
/**
|
||||
* Reloads the extension when notified from the development server. Only enabled
|
||||
@ -10,7 +10,7 @@ async function main(): Promise<void> {
|
||||
const self = await browser.management.getSelf()
|
||||
if (self.installType === 'development') {
|
||||
// Since the port is hard-coded, it must match scripts/dev.ts
|
||||
io.connect('http://localhost:8890').on('file.change', () => browser.runtime.reload())
|
||||
socketIoClient.connect('http://localhost:8890').on('file.change', () => browser.runtime.reload())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ import { createBlobURLForBundle } from '../../shared/platform/worker'
|
||||
import { getHeaders } from '../../shared/backend/headers'
|
||||
import { fromBrowserEvent } from '../web-extension-api/fromBrowserEvent'
|
||||
import { observeSourcegraphURL } from '../../shared/util/context'
|
||||
import { assertEnv } from '../envAssertion'
|
||||
import { assertEnvironment } from '../environmentAssertion'
|
||||
import { observeStorageKey, storage } from '../web-extension-api/storage'
|
||||
import { isDefined } from '../../../../shared/src/util/types'
|
||||
import { browserPortToMessagePort, findMessagePorts } from '../../shared/platform/ports'
|
||||
@ -24,7 +24,7 @@ import { EndpointPair } from '../../../../shared/src/platform/context'
|
||||
|
||||
const IS_EXTENSION = true
|
||||
|
||||
assertEnv('BACKGROUND')
|
||||
assertEnvironment('BACKGROUND')
|
||||
|
||||
initSentry('background')
|
||||
|
||||
@ -232,8 +232,8 @@ async function main(): Promise<void> {
|
||||
next: browserPortPair => {
|
||||
subscriptions.add(handleBrowserPortPair(browserPortPair))
|
||||
},
|
||||
error: err => {
|
||||
console.error('Error handling extension host client connection', err)
|
||||
error: error => {
|
||||
console.error('Error handling extension host client connection', error)
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
@ -16,12 +16,12 @@ import {
|
||||
} from '../../shared/code-hosts/sourcegraph/inject'
|
||||
import { DEFAULT_SOURCEGRAPH_URL, getAssetsURL } from '../../shared/util/context'
|
||||
import { featureFlags } from '../../shared/util/featureFlags'
|
||||
import { assertEnv } from '../envAssertion'
|
||||
import { assertEnvironment } from '../environmentAssertion'
|
||||
|
||||
const subscriptions = new Subscription()
|
||||
window.addEventListener('unload', () => subscriptions.unsubscribe(), { once: true })
|
||||
|
||||
assertEnv('CONTENT')
|
||||
assertEnvironment('CONTENT')
|
||||
|
||||
const codeHost = determineCodeHost()
|
||||
initSentry('content', codeHost?.type)
|
||||
@ -71,7 +71,7 @@ async function main(): Promise<void> {
|
||||
// Add style sheet and wait for it to load to avoid rendering unstyled elements (which causes an
|
||||
// annoying flash/jitter when the stylesheet loads shortly thereafter).
|
||||
const styleSheet = (() => {
|
||||
let styleSheet = document.querySelector('#ext-style-sheet') as HTMLLinkElement | null
|
||||
let styleSheet = document.querySelector<HTMLLinkElement>('#ext-style-sheet')
|
||||
// If does not exist, create
|
||||
if (!styleSheet) {
|
||||
styleSheet = document.createElement('link')
|
||||
|
||||
@ -14,10 +14,10 @@ import { OptionsMenuProps } from '../options-page/OptionsMenu'
|
||||
import { initSentry } from '../../shared/sentry'
|
||||
import { fetchSite } from '../../shared/backend/server'
|
||||
import { featureFlags } from '../../shared/util/featureFlags'
|
||||
import { assertEnv } from '../envAssertion'
|
||||
import { assertEnvironment } from '../environmentAssertion'
|
||||
import { observeSourcegraphURL } from '../../shared/util/context'
|
||||
|
||||
assertEnv('OPTIONS')
|
||||
assertEnvironment('OPTIONS')
|
||||
|
||||
initSentry('options')
|
||||
|
||||
@ -29,7 +29,7 @@ type State = Pick<
|
||||
> & { sourcegraphURL: string | null; isActivated: boolean }
|
||||
|
||||
const keyIsFeatureFlag = (key: string): key is keyof FeatureFlags =>
|
||||
!!Object.keys(featureFlagDefaults).find(k => key === k)
|
||||
!!Object.keys(featureFlagDefaults).find(featureFlag => key === featureFlag)
|
||||
|
||||
const toggleFeatureFlag = (key: string): void => {
|
||||
if (keyIsFeatureFlag(key)) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import expect from 'expect'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/e2e/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/e2e/driver'
|
||||
import { retry } from '../../../shared/src/e2e/e2e-test-utils'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/testing/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/testing/driver'
|
||||
import { retry } from '../../../shared/src/testing/utils'
|
||||
import { ExternalServiceKind } from '../../../shared/src/graphql/schema'
|
||||
import { testSingleFilePage } from './shared'
|
||||
import { getConfig } from '../../../shared/src/e2e/config'
|
||||
import { getConfig } from '../../../shared/src/testing/config'
|
||||
|
||||
// By default, these tests run against a local Bitbucket instance and a local Sourcegraph instance.
|
||||
// You can run them against other instances by setting the below env vars in addition to SOURCEGRAPH_BASE_URL.
|
||||
@ -38,7 +38,7 @@ async function createProject(driver: Driver): Promise<void> {
|
||||
await driver.page.goto(BITBUCKET_BASE_URL + '/projects')
|
||||
await driver.page.waitForSelector('.entity-table')
|
||||
const existingProject = await driver.page.evaluate(() =>
|
||||
[...document.querySelectorAll('span.project-name')].some(p => p.textContent === 'SOURCEGRAPH')
|
||||
[...document.querySelectorAll('span.project-name')].some(project => project.textContent === 'SOURCEGRAPH')
|
||||
)
|
||||
if (existingProject) {
|
||||
return
|
||||
@ -1,8 +1,8 @@
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/e2e/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/e2e/driver'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/testing/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/testing/driver'
|
||||
import { ExternalServiceKind } from '../../../shared/src/graphql/schema'
|
||||
import { testSingleFilePage } from './shared'
|
||||
import { getConfig } from '../../../shared/src/e2e/config'
|
||||
import { getConfig } from '../../../shared/src/testing/config'
|
||||
|
||||
const GHE_BASE_URL = process.env.GHE_BASE_URL || 'https://ghe.sgdev.org'
|
||||
const GHE_USERNAME = process.env.GHE_USERNAME
|
||||
@ -1,10 +1,10 @@
|
||||
import { startCase } from 'lodash'
|
||||
import assert from 'assert'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/e2e/screenshotReporter'
|
||||
import { Driver, createDriverForTest } from '../../../shared/src/e2e/driver'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/testing/screenshotReporter'
|
||||
import { Driver, createDriverForTest } from '../../../shared/src/testing/driver'
|
||||
import { testSingleFilePage } from './shared'
|
||||
import { retry } from '../../../shared/src/e2e/e2e-test-utils'
|
||||
import { getConfig } from '../../../shared/src/e2e/config'
|
||||
import { retry } from '../../../shared/src/testing/utils'
|
||||
import { getConfig } from '../../../shared/src/testing/config'
|
||||
import { fromEvent } from 'rxjs'
|
||||
import { first, filter, timeout, mergeMap } from 'rxjs/operators'
|
||||
import { Target, Page } from 'puppeteer'
|
||||
@ -97,7 +97,7 @@ describe('Sourcegraph browser extension on github.com', function () {
|
||||
)
|
||||
).asElement()
|
||||
assert(tokenElement, 'Expected token element to exist')
|
||||
return tokenElement!
|
||||
return tokenElement
|
||||
})
|
||||
// Retry is here to wait for listeners to be registered
|
||||
await retry(async () => {
|
||||
@ -1,8 +1,8 @@
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/e2e/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/e2e/driver'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/testing/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/testing/driver'
|
||||
import { ExternalServiceKind } from '../../../shared/src/graphql/schema'
|
||||
import { testSingleFilePage } from './shared'
|
||||
import { getConfig } from '../../../shared/src/e2e/config'
|
||||
import { getConfig } from '../../../shared/src/testing/config'
|
||||
|
||||
// By default, these tests run against gitlab.com and a local Sourcegraph instance.
|
||||
// You can run them against other instances by setting the below env vars in addition to SOURCEGRAPH_BASE_URL.
|
||||
@ -1,11 +1,11 @@
|
||||
import expect from 'expect'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/e2e/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/e2e/driver'
|
||||
import { saveScreenshotsUponFailures } from '../../../shared/src/testing/screenshotReporter'
|
||||
import { createDriverForTest, Driver } from '../../../shared/src/testing/driver'
|
||||
import { ExternalServiceKind } from '../../../shared/src/graphql/schema'
|
||||
import { PhabricatorMapping } from '../browser-extension/web-extension-api/types'
|
||||
import { isEqual } from 'lodash'
|
||||
import { getConfig } from '../../../shared/src/e2e/config'
|
||||
import { retry } from '../../../shared/src/e2e/e2e-test-utils'
|
||||
import { getConfig } from '../../../shared/src/testing/config'
|
||||
import { retry } from '../../../shared/src/testing/utils'
|
||||
|
||||
// By default, these tests run against a local Phabricator instance and a local Sourcegraph instance.
|
||||
// To run them against phabricator.sgdev.org and umami.sgdev.org, set the below env vars in addition to SOURCEGRAPH_BASE_URL.
|
||||
@ -108,10 +108,10 @@ async function configureSourcegraphIntegration(driver: Driver): Promise<void> {
|
||||
|
||||
// Configure the repository mappings
|
||||
await driver.page.goto(PHABRICATOR_BASE_URL + '/config/edit/sourcegraph.callsignMappings/')
|
||||
const callSignConfigStr = await driver.page.evaluate(() =>
|
||||
const callSignConfigString = await driver.page.evaluate(() =>
|
||||
document.querySelector<HTMLTextAreaElement>('textarea[name="value"]')!.value.trim()
|
||||
)
|
||||
const callSignConfig: PhabricatorMapping[] = (callSignConfigStr && JSON.parse(callSignConfigStr)) || []
|
||||
const callSignConfig: PhabricatorMapping[] = (callSignConfigString && JSON.parse(callSignConfigString)) || []
|
||||
const jsonRpc2Mapping: PhabricatorMapping = {
|
||||
path: 'github.com/sourcegraph/jsonrpc2',
|
||||
callsign: 'JRPC',
|
||||
@ -1,6 +1,6 @@
|
||||
import expect from 'expect'
|
||||
import { Driver } from '../../../shared/src/e2e/driver'
|
||||
import { retry } from '../../../shared/src/e2e/e2e-test-utils'
|
||||
import { Driver } from '../../../shared/src/testing/driver'
|
||||
import { retry } from '../../../shared/src/testing/utils'
|
||||
import assert from 'assert'
|
||||
|
||||
/**
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"references": [{ "path": "../.." }, { "path": "../../../shared/src/e2e" }],
|
||||
"references": [{ "path": "../.." }, { "path": "../../../shared/src/testing" }],
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "node"],
|
||||
"module": "commonjs",
|
||||
"rootDir": ".",
|
||||
"outDir": "../../out/src/e2e",
|
||||
"outDir": "../../out/src/end-to-end",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "ts-graphql-plugin",
|
||||
@ -5,12 +5,12 @@
|
||||
* TODO could this be eliminated with shadow DOM?
|
||||
*/
|
||||
export function metaClickOverride(): void {
|
||||
const JX = (window as any).JX
|
||||
if (JX.Stratcom._dispatchProxyPreMeta) {
|
||||
const javelin = (window as any).JX
|
||||
if (javelin.Stratcom._dispatchProxyPreMeta) {
|
||||
return
|
||||
}
|
||||
JX.Stratcom._dispatchProxyPreMeta = JX.Stratcom._dispatchProxy
|
||||
JX.Stratcom._dispatchProxy = (proxyEvent: {
|
||||
javelin.Stratcom._dispatchProxyPreMeta = javelin.Stratcom._dispatchProxy
|
||||
javelin.Stratcom._dispatchProxy = (proxyEvent: {
|
||||
__auto__type: string
|
||||
__auto__rawEvent: KeyboardEvent
|
||||
__auto__target: HTMLElement
|
||||
@ -22,6 +22,6 @@ export function metaClickOverride(): void {
|
||||
) {
|
||||
return
|
||||
}
|
||||
return JX.Stratcom._dispatchProxyPreMeta(proxyEvent)
|
||||
return javelin.Stratcom._dispatchProxyPreMeta(proxyEvent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
import { TextDocumentIdentifier } from '../../../../shared/src/api/client/types/textDocument'
|
||||
import { TextDocumentPositionParams } from '../../../../shared/src/api/protocol'
|
||||
import { AbsoluteRepoFilePosition, FileSpec, RepoSpec, ResolvedRevSpec } from '../../../../shared/src/util/url'
|
||||
|
||||
export const toTextDocumentIdentifier = (pos: RepoSpec & ResolvedRevSpec & FileSpec): TextDocumentIdentifier => ({
|
||||
uri: `git://${pos.repoName}?${pos.commitID}#${pos.filePath}`,
|
||||
})
|
||||
|
||||
export const toTextDocumentPositionParams = (pos: AbsoluteRepoFilePosition): TextDocumentPositionParams => ({
|
||||
textDocument: toTextDocumentIdentifier(pos),
|
||||
position: {
|
||||
character: pos.position.character - 1,
|
||||
line: pos.position.line - 1,
|
||||
},
|
||||
})
|
||||
17
browser/src/shared/backend/extension-api-conversion.tsx
Normal file
17
browser/src/shared/backend/extension-api-conversion.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { TextDocumentIdentifier } from '../../../../shared/src/api/client/types/textDocument'
|
||||
import { TextDocumentPositionParams } from '../../../../shared/src/api/protocol'
|
||||
import { AbsoluteRepoFilePosition, FileSpec, RepoSpec, ResolvedRevisionSpec } from '../../../../shared/src/util/url'
|
||||
|
||||
export const toTextDocumentIdentifier = (
|
||||
position: RepoSpec & ResolvedRevisionSpec & FileSpec
|
||||
): TextDocumentIdentifier => ({
|
||||
uri: `git://${position.repoName}?${position.commitID}#${position.filePath}`,
|
||||
})
|
||||
|
||||
export const toTextDocumentPositionParameters = (position: AbsoluteRepoFilePosition): TextDocumentPositionParams => ({
|
||||
textDocument: toTextDocumentIdentifier(position),
|
||||
position: {
|
||||
character: position.position.character - 1,
|
||||
line: position.position.line - 1,
|
||||
},
|
||||
})
|
||||
@ -80,9 +80,9 @@ function createSuggestion(item: GQL.SearchSuggestion): Suggestion | null {
|
||||
}
|
||||
case 'File': {
|
||||
const descriptionParts: string[] = []
|
||||
const dir = dirname(item.path)
|
||||
if (dir !== undefined && dir !== '.') {
|
||||
descriptionParts.push(`${dir}/`)
|
||||
const directory = dirname(item.path)
|
||||
if (directory !== undefined && directory !== '.') {
|
||||
descriptionParts.push(`${directory}/`)
|
||||
}
|
||||
descriptionParts.push(basename(item.repository.name))
|
||||
if (item.isDirectory) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { AdjustmentDirection, PositionAdjuster } from '@sourcegraph/codeintellify'
|
||||
import { of } from 'rxjs'
|
||||
import { Omit } from 'utility-types'
|
||||
import { FileSpec, RepoSpec, ResolvedRevSpec, RevSpec } from '../../../../../shared/src/util/url'
|
||||
import { FileSpec, RepoSpec, ResolvedRevisionSpec, RevisionSpec } from '../../../../../shared/src/util/url'
|
||||
import { querySelectorOrSelf } from '../../util/dom'
|
||||
import { CodeHost, MountGetter } from '../shared/codeHost'
|
||||
import { CodeView, DOMFunctions } from '../shared/codeViews'
|
||||
@ -48,28 +48,32 @@ export const getToolbarMount = (codeView: HTMLElement): HTMLElement => {
|
||||
*/
|
||||
const createPositionAdjuster = (
|
||||
dom: DOMFunctions
|
||||
): PositionAdjuster<RepoSpec & RevSpec & FileSpec & ResolvedRevSpec> => ({ direction, codeView, position }) => {
|
||||
): PositionAdjuster<RepoSpec & RevisionSpec & FileSpec & ResolvedRevisionSpec> => ({
|
||||
direction,
|
||||
codeView,
|
||||
position,
|
||||
}) => {
|
||||
const codeElement = dom.getCodeElementFromLineNumber(codeView, position.line, position.part)
|
||||
if (!codeElement) {
|
||||
throw new Error('(adjustPosition) could not find code element for line provided')
|
||||
}
|
||||
|
||||
let delta = 0
|
||||
for (const modifiedTextElem of codeElement.querySelectorAll('[cm-text]')) {
|
||||
const actualText = modifiedTextElem.getAttribute('cm-text') || ''
|
||||
const adjustedText = modifiedTextElem.textContent || ''
|
||||
for (const modifiedTextElement of codeElement.querySelectorAll('[cm-text]')) {
|
||||
const actualText = modifiedTextElement.getAttribute('cm-text') || ''
|
||||
const adjustedText = modifiedTextElement.textContent || ''
|
||||
|
||||
delta += actualText.length - adjustedText.length
|
||||
}
|
||||
|
||||
const modifier = direction === AdjustmentDirection.ActualToCodeView ? -1 : 1
|
||||
|
||||
const newPos = {
|
||||
const newPosition = {
|
||||
line: position.line,
|
||||
character: position.character + modifier * delta,
|
||||
}
|
||||
|
||||
return of(newPos)
|
||||
return of(newPosition)
|
||||
}
|
||||
|
||||
const toolbarButtonProps = {
|
||||
@ -155,14 +159,14 @@ const getCommandPaletteMount: MountGetter = (container: HTMLElement): HTMLElemen
|
||||
if (!headerElement) {
|
||||
return null
|
||||
}
|
||||
const classes = ['command-palette-button', 'command-palette-button--bitbucket-server']
|
||||
const classNames = ['command-palette-button', 'command-palette-button--bitbucket-server']
|
||||
const create = (): HTMLElement => {
|
||||
const mount = document.createElement('li')
|
||||
mount.className = classes.join(' ')
|
||||
mount.className = classNames.join(' ')
|
||||
headerElement.append(mount)
|
||||
return mount
|
||||
}
|
||||
const preexisting = headerElement.querySelector<HTMLElement>(classes.map(c => `.${c}`).join(''))
|
||||
const preexisting = headerElement.querySelector<HTMLElement>(classNames.map(className => `.${className}`).join(''))
|
||||
return preexisting || create()
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { RawRepoSpec, RevSpec } from '../../../../../shared/src/util/url'
|
||||
import { RawRepoSpec, RevisionSpec } from '../../../../../shared/src/util/url'
|
||||
import { CodeHostContext } from '../shared/codeHost'
|
||||
|
||||
// example pathname: /projects/TEST/repos/some-repo/browse/src/extension.ts
|
||||
@ -20,39 +20,41 @@ interface RevisionRefInfo {
|
||||
latestCommit?: string
|
||||
}
|
||||
|
||||
function getRevSpecFromRevisionSelector(): RevSpec {
|
||||
function getRevisionSpecFromRevisionSelector(): RevisionSpec {
|
||||
const branchNameElement = document.querySelector('#repository-layout-revision-selector .name[data-revision-ref]')
|
||||
if (!branchNameElement) {
|
||||
throw new Error('branchNameElement not found')
|
||||
}
|
||||
const revisionRefStr = branchNameElement.getAttribute('data-revision-ref')
|
||||
let revisionRefInfo: RevisionRefInfo | null = null
|
||||
if (revisionRefStr) {
|
||||
const revisionReferenceString = branchNameElement.getAttribute('data-revision-ref')
|
||||
let revisionReferenceInfo: RevisionRefInfo | null = null
|
||||
if (revisionReferenceString) {
|
||||
try {
|
||||
revisionRefInfo = JSON.parse(revisionRefStr)
|
||||
revisionReferenceInfo = JSON.parse(revisionReferenceString)
|
||||
} catch {
|
||||
throw new Error(`Could not parse revisionRefStr: ${revisionRefStr}`)
|
||||
throw new Error(`Could not parse revisionRefStr: ${revisionReferenceString}`)
|
||||
}
|
||||
}
|
||||
if (revisionRefInfo?.latestCommit) {
|
||||
if (revisionReferenceInfo?.latestCommit) {
|
||||
return {
|
||||
rev: revisionRefInfo.latestCommit,
|
||||
revision: revisionReferenceInfo.latestCommit,
|
||||
}
|
||||
}
|
||||
throw new Error(`revisionRefInfo is empty or has no latestCommit (revisionRefStr: ${String(revisionRefStr)})`)
|
||||
throw new Error(
|
||||
`revisionRefInfo is empty or has no latestCommit (revisionRefStr: ${String(revisionReferenceString)})`
|
||||
)
|
||||
}
|
||||
|
||||
export function getContext(): CodeHostContext {
|
||||
const repoSpec = getRawRepoSpecFromLocation(window.location)
|
||||
let revSpec: Partial<RevSpec> = {}
|
||||
let revisionSpec: Partial<RevisionSpec> = {}
|
||||
try {
|
||||
revSpec = getRevSpecFromRevisionSelector()
|
||||
revisionSpec = getRevisionSpecFromRevisionSelector()
|
||||
} catch {
|
||||
// RevSpec is optional in CodeHostContext
|
||||
}
|
||||
return {
|
||||
...repoSpec,
|
||||
...revSpec,
|
||||
...revisionSpec,
|
||||
privateRepository: window.location.hostname !== 'bitbucket.org',
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,17 +2,17 @@ import { DiffPart } from '@sourcegraph/codeintellify'
|
||||
import { DOMFunctions } from '../shared/codeViews'
|
||||
|
||||
const getSingleFileLineElementFromLineNumber = (codeView: HTMLElement, line: number): HTMLElement => {
|
||||
const lineNumElem = codeView.querySelector<HTMLElement>(`[data-line-number="${line}"]`)
|
||||
if (!lineNumElem) {
|
||||
const lineNumberElement = codeView.querySelector<HTMLElement>(`[data-line-number="${line}"]`)
|
||||
if (!lineNumberElement) {
|
||||
throw new Error(`Line ${line} not found in code view`)
|
||||
}
|
||||
|
||||
const lineElem = lineNumElem.closest<HTMLElement>('.line')
|
||||
if (!lineElem) {
|
||||
const lineElement = lineNumberElement.closest<HTMLElement>('.line')
|
||||
if (!lineElement) {
|
||||
throw new Error('Could not find line elem for line element')
|
||||
}
|
||||
|
||||
return lineElem
|
||||
return lineElement
|
||||
}
|
||||
|
||||
export const singleFileDOMFunctions: DOMFunctions = {
|
||||
@ -27,17 +27,17 @@ export const singleFileDOMFunctions: DOMFunctions = {
|
||||
throw new Error('Could not find line containing code element')
|
||||
}
|
||||
|
||||
const lineNumElem = line.querySelector<HTMLElement>('.line-locator')
|
||||
if (!lineNumElem) {
|
||||
const lineNumberElement = line.querySelector<HTMLElement>('.line-locator')
|
||||
if (!lineNumberElement) {
|
||||
throw new Error('Could not find the line number in a line container')
|
||||
}
|
||||
|
||||
const lineNum = parseInt(lineNumElem.dataset.lineNumber || '', 10)
|
||||
if (isNaN(lineNum)) {
|
||||
const lineNumber = parseInt(lineNumberElement.dataset.lineNumber || '', 10)
|
||||
if (isNaN(lineNumber)) {
|
||||
throw new TypeError('data-line-number not set on line number element')
|
||||
}
|
||||
|
||||
return lineNum
|
||||
return lineNumber
|
||||
},
|
||||
getLineElementFromLineNumber: getSingleFileLineElementFromLineNumber,
|
||||
getCodeElementFromLineNumber: (codeView, line) =>
|
||||
@ -47,15 +47,15 @@ export const singleFileDOMFunctions: DOMFunctions = {
|
||||
}
|
||||
|
||||
const getDiffLineElementFromLineNumber = (codeView: HTMLElement, line: number, part?: DiffPart): HTMLElement => {
|
||||
for (const lineNumElem of codeView.querySelectorAll(`.line-number-${part === 'head' ? 'to' : 'from'}`)) {
|
||||
const lineNum = parseInt((lineNumElem.textContent || '').trim(), 10)
|
||||
if (!isNaN(lineNum) && lineNum === line) {
|
||||
const lineElem = lineNumElem.closest<HTMLElement>('.line')
|
||||
if (!lineElem) {
|
||||
for (const lineNumberElement of codeView.querySelectorAll(`.line-number-${part === 'head' ? 'to' : 'from'}`)) {
|
||||
const lineNumber = parseInt((lineNumberElement.textContent || '').trim(), 10)
|
||||
if (!isNaN(lineNumber) && lineNumber === line) {
|
||||
const lineElement = lineNumberElement.closest<HTMLElement>('.line')
|
||||
if (!lineElement) {
|
||||
throw new Error('Could not find lineElem from lineNumElem')
|
||||
}
|
||||
|
||||
return lineElem
|
||||
return lineElement
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,19 +70,19 @@ export const diffDOMFunctions: DOMFunctions = {
|
||||
throw new Error('Could not find line containing code element')
|
||||
}
|
||||
|
||||
const lineNumTo = line.querySelector<HTMLElement>('.line-number-to')
|
||||
if (lineNumTo) {
|
||||
const lineNum = parseInt((lineNumTo.textContent || '').trim(), 10)
|
||||
if (!isNaN(lineNum)) {
|
||||
return lineNum
|
||||
const lineNumberTo = line.querySelector<HTMLElement>('.line-number-to')
|
||||
if (lineNumberTo) {
|
||||
const lineNumber = parseInt((lineNumberTo.textContent || '').trim(), 10)
|
||||
if (!isNaN(lineNumber)) {
|
||||
return lineNumber
|
||||
}
|
||||
}
|
||||
|
||||
const lineNumFrom = line.querySelector<HTMLElement>('.line-number-from')
|
||||
if (lineNumFrom) {
|
||||
const lineNum = parseInt((lineNumFrom.textContent || '').trim(), 10)
|
||||
if (!isNaN(lineNum)) {
|
||||
return lineNum
|
||||
const lineNumberFrom = line.querySelector<HTMLElement>('.line-number-from')
|
||||
if (lineNumberFrom) {
|
||||
const lineNumber = parseInt((lineNumberFrom.textContent || '').trim(), 10)
|
||||
if (!isNaN(lineNumber)) {
|
||||
return lineNumber
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ describe('Bitbucket scrape.ts', () => {
|
||||
project: 'SOUR',
|
||||
rawRepoName: 'bitbucket.test/SOUR/mux',
|
||||
repoSlug: 'mux',
|
||||
rev: 'master',
|
||||
revision: 'master',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -23,11 +23,11 @@ const bitbucketToSourcegraphRepoName = ({ repoSlug, project }: BitbucketRepoInfo
|
||||
* - project name
|
||||
* - repo name
|
||||
* - file path
|
||||
* - rev (through the query parameter `at`)
|
||||
* - revision (through the query parameter `at`)
|
||||
*/
|
||||
const getFileInfoFromLinkInSingleFileView = (
|
||||
codeView: HTMLElement
|
||||
): Pick<FileInfo, 'rawRepoName' | 'filePath' | 'rev'> & BitbucketRepoInfo => {
|
||||
): Pick<FileInfo, 'rawRepoName' | 'filePath' | 'revision'> & BitbucketRepoInfo => {
|
||||
const errors: Error[] = []
|
||||
for (const selector of LINK_SELECTORS) {
|
||||
try {
|
||||
@ -38,7 +38,7 @@ const getFileInfoFromLinkInSingleFileView = (
|
||||
const url = new URL(linkElement.href)
|
||||
const path = url.pathname
|
||||
|
||||
// Looks like /projects/<project>/repos/<repo>/(browse|raw)/<file path>?at=<rev>
|
||||
// Looks like /projects/<project>/repos/<repo>/(browse|raw)/<file path>?at=<revision>
|
||||
const pathMatch = path.match(/\/projects\/(.*?)\/repos\/(.*?)\/(?:browse|raw)\/(.*)$/)
|
||||
if (!pathMatch) {
|
||||
throw new Error(`Path of link matching selector ${selector} did not match path regex: ${path}`)
|
||||
@ -46,22 +46,22 @@ const getFileInfoFromLinkInSingleFileView = (
|
||||
|
||||
const [, project, repoSlug, filePath] = pathMatch
|
||||
|
||||
// Looks like 'refs/heads/<rev>'
|
||||
const at = url.searchParams.get('at')
|
||||
if (!at) {
|
||||
// Looks like 'refs/heads/<revision>'
|
||||
const atParameter = url.searchParams.get('at')
|
||||
if (!atParameter) {
|
||||
throw new Error(
|
||||
`href of link matching selector ${selector} did not have 'at' search param: ${url.href}`
|
||||
)
|
||||
}
|
||||
|
||||
const atMatch = at.match(/refs\/heads\/(.*?)$/)
|
||||
const atMatch = atParameter.match(/refs\/heads\/(.*?)$/)
|
||||
|
||||
const rev = atMatch ? atMatch[1] : at
|
||||
const revision = atMatch ? atMatch[1] : atParameter
|
||||
|
||||
return {
|
||||
rawRepoName: bitbucketToSourcegraphRepoName({ repoSlug, project }),
|
||||
filePath: decodeURIComponent(filePath),
|
||||
rev,
|
||||
revision,
|
||||
project,
|
||||
repoSlug,
|
||||
}
|
||||
@ -107,13 +107,13 @@ const getCommitIDFromRevisionSelector = (): string => {
|
||||
*/
|
||||
export const getFileInfoFromSingleFileSourceCodeView = (
|
||||
codeViewElement: HTMLElement
|
||||
): BitbucketRepoInfo & Pick<FileInfo, 'rawRepoName' | 'filePath' | 'rev' | 'commitID'> => {
|
||||
const { rawRepoName, filePath, rev, project, repoSlug } = getFileInfoFromLinkInSingleFileView(codeViewElement)
|
||||
): BitbucketRepoInfo & Pick<FileInfo, 'rawRepoName' | 'filePath' | 'revision' | 'commitID'> => {
|
||||
const { rawRepoName, filePath, revision, project, repoSlug } = getFileInfoFromLinkInSingleFileView(codeViewElement)
|
||||
const commitID = getCommitIDFromRevisionSelector()
|
||||
return {
|
||||
rawRepoName,
|
||||
filePath,
|
||||
rev,
|
||||
revision,
|
||||
commitID,
|
||||
project,
|
||||
repoSlug,
|
||||
@ -161,7 +161,7 @@ const getChangeType = ({ changeTypeElement }: { changeTypeElement: HTMLElement |
|
||||
if (!changeTypeElement) {
|
||||
return 'MODIFY'
|
||||
}
|
||||
const className = [...changeTypeElement.classList].find(c => /^change-type-[A-Z]+/.test(c))
|
||||
const className = [...changeTypeElement.classList].find(className => /^change-type-[A-Z]+/.test(className))
|
||||
if (!className) {
|
||||
throw new Error('Could not detect change type from change type element')
|
||||
}
|
||||
@ -261,7 +261,7 @@ export const getFileInfoFromSingleFileDiffCodeView = (
|
||||
*/
|
||||
export const getFileInfoWithoutCommitIDsFromMultiFileDiffCodeView = (
|
||||
codeViewElement: HTMLElement
|
||||
): BitbucketRepoInfo & Pick<FileInfo, 'rawRepoName' | 'baseRawRepoName' | 'filePath' | 'baseFilePath' | 'rev'> => {
|
||||
): BitbucketRepoInfo & Pick<FileInfo, 'rawRepoName' | 'baseRawRepoName' | 'filePath' | 'baseFilePath' | 'revision'> => {
|
||||
// Get the file path from the breadcrumbs
|
||||
const breadcrumbsElement = codeViewElement.querySelector('.breadcrumbs')
|
||||
if (!breadcrumbsElement) {
|
||||
@ -301,7 +301,7 @@ export const getFileInfoFromCommitDiffCodeView = (
|
||||
): BitbucketRepoInfo &
|
||||
Pick<
|
||||
FileInfo,
|
||||
'rawRepoName' | 'baseRawRepoName' | 'filePath' | 'baseFilePath' | 'rev' | 'commitID' | 'baseCommitID'
|
||||
'rawRepoName' | 'baseRawRepoName' | 'filePath' | 'baseFilePath' | 'revision' | 'commitID' | 'baseCommitID'
|
||||
> => {
|
||||
const commitID = getCommitIDFromLink('.commit-badge-oneline .commitid')
|
||||
const baseCommitID = getCommitIDFromLink('.commit-parents .commitid')
|
||||
|
||||
@ -4,7 +4,7 @@ exports[`util parseURL() blob page 1`] = `
|
||||
Object {
|
||||
"pageType": "blob",
|
||||
"rawRepoName": "github.com/sourcegraph/sourcegraph",
|
||||
"revAndFilePath": "3.3/shared/src/hover/HoverOverlay.tsx",
|
||||
"revisionAndFilePath": "3.3/shared/src/hover/HoverOverlay.tsx",
|
||||
}
|
||||
`;
|
||||
|
||||
@ -12,7 +12,7 @@ exports[`util parseURL() branch name with forward slashes 1`] = `
|
||||
Object {
|
||||
"pageType": "blob",
|
||||
"rawRepoName": "ghe.sgdev.org/beyang/mux",
|
||||
"revAndFilePath": "jr/branch/mux.go",
|
||||
"revisionAndFilePath": "jr/branch/mux.go",
|
||||
}
|
||||
`;
|
||||
|
||||
@ -48,7 +48,7 @@ exports[`util parseURL() selections - range 1`] = `
|
||||
Object {
|
||||
"pageType": "blob",
|
||||
"rawRepoName": "github.com/sourcegraph/sourcegraph",
|
||||
"revAndFilePath": "master/jest.config.base.js",
|
||||
"revisionAndFilePath": "master/jest.config.base.js",
|
||||
}
|
||||
`;
|
||||
|
||||
@ -56,7 +56,7 @@ exports[`util parseURL() selections - single line 1`] = `
|
||||
Object {
|
||||
"pageType": "blob",
|
||||
"rawRepoName": "github.com/sourcegraph/sourcegraph",
|
||||
"revAndFilePath": "master/jest.config.base.js",
|
||||
"revisionAndFilePath": "master/jest.config.base.js",
|
||||
}
|
||||
`;
|
||||
|
||||
@ -64,7 +64,7 @@ exports[`util parseURL() snippet permalink 1`] = `
|
||||
Object {
|
||||
"pageType": "blob",
|
||||
"rawRepoName": "github.com/sourcegraph/sourcegraph",
|
||||
"revAndFilePath": "6a91ccec97a46bfb511b7ff58d790554a7d075c8/client/browser/src/shared/repo/backend.tsx",
|
||||
"revisionAndFilePath": "6a91ccec97a46bfb511b7ff58d790554a7d075c8/client/browser/src/shared/repo/backend.tsx",
|
||||
}
|
||||
`;
|
||||
|
||||
@ -72,7 +72,7 @@ exports[`util parseURL() tree page 1`] = `
|
||||
Object {
|
||||
"pageType": "tree",
|
||||
"rawRepoName": "github.com/sourcegraph/sourcegraph",
|
||||
"revAndFilePath": "master/client",
|
||||
"revisionAndFilePath": "master/client",
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ describe('github/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/sourcegraph',
|
||||
rawRepoName: 'github.com/sourcegraph/sourcegraph',
|
||||
rev: 'master',
|
||||
revision: 'master',
|
||||
filePath: 'browser/src/shared/code-hosts/code_intelligence.tsx',
|
||||
position: {
|
||||
line: 5,
|
||||
@ -105,7 +105,7 @@ describe('github/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/sourcegraph',
|
||||
rawRepoName: 'ghe.sgdev.org/sourcegraph/sourcegraph',
|
||||
rev: 'master',
|
||||
revision: 'master',
|
||||
filePath: 'browser/src/shared/code-hosts/code_intelligence.tsx',
|
||||
position: {
|
||||
line: 5,
|
||||
@ -125,7 +125,7 @@ describe('github/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/sourcegraph',
|
||||
rawRepoName: 'github.com/sourcegraph/sourcegraph',
|
||||
rev: 'master',
|
||||
revision: 'master',
|
||||
filePath: 'browser/src/shared/code-hosts/code_intelligence.tsx',
|
||||
position: {
|
||||
line: 5,
|
||||
@ -154,7 +154,7 @@ describe('github/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/sourcegraph',
|
||||
rawRepoName: 'github.com/sourcegraph/sourcegraph',
|
||||
rev: 'core/gitserver-tracing',
|
||||
revision: 'core/gitserver-tracing',
|
||||
filePath: 'cmd/gitserver/server/server.go',
|
||||
position: {
|
||||
line: 1335,
|
||||
|
||||
@ -3,7 +3,13 @@ import { trimStart } from 'lodash'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { Omit } from 'utility-types'
|
||||
import { PlatformContext } from '../../../../../shared/src/platform/context'
|
||||
import { FileSpec, RepoSpec, ResolvedRevSpec, RevSpec, toAbsoluteBlobURL } from '../../../../../shared/src/util/url'
|
||||
import {
|
||||
FileSpec,
|
||||
RepoSpec,
|
||||
ResolvedRevisionSpec,
|
||||
RevisionSpec,
|
||||
toAbsoluteBlobURL,
|
||||
} from '../../../../../shared/src/util/url'
|
||||
import { fetchBlobContentLines } from '../../repo/backend'
|
||||
import { querySelectorOrSelf } from '../../util/dom'
|
||||
import { CodeHost, MountGetter } from '../shared/codeHost'
|
||||
@ -33,8 +39,8 @@ export function createFileActionsToolbarMount(codeView: HTMLElement): HTMLElemen
|
||||
return existingMount
|
||||
}
|
||||
|
||||
const mountEl = document.createElement('div')
|
||||
mountEl.className = className
|
||||
const mountElement = document.createElement('div')
|
||||
mountElement.className = className
|
||||
|
||||
const fileActions = codeView.querySelector('.file-actions')
|
||||
if (!fileActions) {
|
||||
@ -48,12 +54,12 @@ export function createFileActionsToolbarMount(codeView: HTMLElement): HTMLElemen
|
||||
// Old GitHub Enterprise PR views have a "☑ show comments" text that we want to insert *after*
|
||||
const showCommentsElement = codeView.querySelector('.show-file-notes')
|
||||
if (showCommentsElement) {
|
||||
showCommentsElement.after(mountEl)
|
||||
showCommentsElement.after(mountElement)
|
||||
} else {
|
||||
fileActions.prepend(mountEl)
|
||||
fileActions.prepend(mountElement)
|
||||
}
|
||||
|
||||
return mountEl
|
||||
return mountElement
|
||||
}
|
||||
|
||||
const toolbarButtonProps = {
|
||||
@ -94,7 +100,7 @@ const singleFileCodeView: Omit<CodeView, 'element'> = {
|
||||
*/
|
||||
const getSnippetPositionAdjuster = (
|
||||
requestGraphQL: PlatformContext['requestGraphQL']
|
||||
): PositionAdjuster<RepoSpec & RevSpec & FileSpec & ResolvedRevSpec> => ({ direction, codeView, position }) =>
|
||||
): PositionAdjuster<RepoSpec & RevisionSpec & FileSpec & ResolvedRevisionSpec> => ({ direction, codeView, position }) =>
|
||||
fetchBlobContentLines({ ...position, requestGraphQL }).pipe(
|
||||
map(lines => {
|
||||
const codeElement = singleFileDOMFunctions.getCodeElementFromLineNumber(
|
||||
@ -143,18 +149,18 @@ export const createFileLineContainerToolbarMount: NonNullable<CodeView['getToolb
|
||||
if (existingMount) {
|
||||
return existingMount
|
||||
}
|
||||
const mountEl = document.createElement('div')
|
||||
mountEl.style.display = 'inline-flex'
|
||||
mountEl.style.verticalAlign = 'middle'
|
||||
mountEl.style.alignItems = 'center'
|
||||
mountEl.className = className
|
||||
const mountElement = document.createElement('div')
|
||||
mountElement.style.display = 'inline-flex'
|
||||
mountElement.style.verticalAlign = 'middle'
|
||||
mountElement.style.alignItems = 'center'
|
||||
mountElement.className = className
|
||||
const rawURLLink = codeViewElement.querySelector('#raw-url')
|
||||
const buttonGroup = rawURLLink?.closest('.BtnGroup')
|
||||
if (!buttonGroup?.parentNode) {
|
||||
throw new Error('File actions not found')
|
||||
}
|
||||
buttonGroup.parentNode.insertBefore(mountEl, buttonGroup)
|
||||
return mountEl
|
||||
buttonGroup.parentNode.insertBefore(mountElement, buttonGroup)
|
||||
return mountElement
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,14 +201,14 @@ export const fileLineContainerResolver: ViewResolver<CodeView> = {
|
||||
|
||||
const genericCodeViewResolver: ViewResolver<CodeView> = {
|
||||
selector: '.file',
|
||||
resolveView: (elem: HTMLElement): CodeView | null => {
|
||||
if (elem.querySelector('article.markdown-body')) {
|
||||
resolveView: (element: HTMLElement): CodeView | null => {
|
||||
if (element.querySelector('article.markdown-body')) {
|
||||
// This code view is rendered markdown, we shouldn't add code intelligence
|
||||
return null
|
||||
}
|
||||
|
||||
// This is a suggested change on a GitHub PR
|
||||
if (elem.closest('.js-suggested-changes-blob')) {
|
||||
if (element.closest('.js-suggested-changes-blob')) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -213,15 +219,15 @@ const genericCodeViewResolver: ViewResolver<CodeView> = {
|
||||
document.querySelectorAll('.diff-view').length === 0
|
||||
|
||||
if (isSingleCodeFile) {
|
||||
return { element: elem, ...singleFileCodeView }
|
||||
return { element, ...singleFileCodeView }
|
||||
}
|
||||
|
||||
if (elem.closest('.discussion-item-body') || elem.classList.contains('js-comment-container')) {
|
||||
if (element.closest('.discussion-item-body') || element.classList.contains('js-comment-container')) {
|
||||
// This code view is embedded on a PR conversation page.
|
||||
return { element: elem, ...diffConversationCodeView }
|
||||
return { element, ...diffConversationCodeView }
|
||||
}
|
||||
|
||||
return { element: elem, ...diffCodeView }
|
||||
return { element, ...diffCodeView }
|
||||
},
|
||||
}
|
||||
|
||||
@ -287,7 +293,8 @@ export const githubCodeHost: CodeHost = {
|
||||
const parsedURL = parseURL()
|
||||
return {
|
||||
...parsedURL,
|
||||
rev: parsedURL.pageType === 'blob' || parsedURL.pageType === 'tree' ? resolveFileInfo().rev : undefined,
|
||||
revision:
|
||||
parsedURL.pageType === 'blob' || parsedURL.pageType === 'tree' ? resolveFileInfo().revision : undefined,
|
||||
privateRepository: window.location.hostname !== 'github.com' || repoHeaderHasPrivateMarker,
|
||||
}
|
||||
},
|
||||
@ -357,12 +364,12 @@ export const githubCodeHost: CodeHost = {
|
||||
return toAbsoluteBlobURL(sourcegraphURL, target)
|
||||
}
|
||||
|
||||
const rev = target.rev || 'HEAD'
|
||||
const revision = target.revision || 'HEAD'
|
||||
// If we're provided options, we can make the j2d URL more specific.
|
||||
const { rawRepoName } = parseURL()
|
||||
|
||||
// Stay on same page in PR if possible.
|
||||
// TODO to be entirely correct, this would need to compare the rev of the code view with the target rev.
|
||||
// TODO to be entirely correct, this would need to compare the revision of the code view with the target revision.
|
||||
const isSameRepo = rawRepoName === target.rawRepoName
|
||||
if (isSameRepo && context.part !== undefined) {
|
||||
const containers = getFileContainers()
|
||||
@ -397,7 +404,7 @@ export const githubCodeHost: CodeHost = {
|
||||
const fragment = target.position
|
||||
? `#L${target.position.line}${target.position.character ? ':' + target.position.character : ''}`
|
||||
: ''
|
||||
return `https://${target.rawRepoName}/blob/${rev}/${target.filePath}${fragment}`
|
||||
return `https://${target.rawRepoName}/blob/${revision}/${target.filePath}${fragment}`
|
||||
},
|
||||
codeViewsRequireTokenization: true,
|
||||
}
|
||||
|
||||
@ -3,13 +3,13 @@ import { DOMFunctions } from '../shared/codeViews'
|
||||
import { isDiffPageType, parseURL } from './util'
|
||||
|
||||
const getDiffCodePart = (codeElement: HTMLElement): DiffPart => {
|
||||
const td = codeElement.closest('td')!
|
||||
const tableCell = codeElement.closest('td')!
|
||||
|
||||
if (td.classList.contains('blob-code-addition')) {
|
||||
if (tableCell.classList.contains('blob-code-addition')) {
|
||||
return 'head'
|
||||
}
|
||||
|
||||
if (td.classList.contains('blob-code-deletion')) {
|
||||
if (tableCell.classList.contains('blob-code-deletion')) {
|
||||
return 'base'
|
||||
}
|
||||
// If we can't determine the diff part the code element's parent `<td>`
|
||||
@ -18,7 +18,7 @@ const getDiffCodePart = (codeElement: HTMLElement): DiffPart => {
|
||||
// code view to determine whether this is a split or unified diff view.
|
||||
if (isDomSplitDiff(codeElement)) {
|
||||
// If there are more cells on the right, this is the base, otherwise the head
|
||||
return td.nextElementSibling ? 'base' : 'head'
|
||||
return tableCell.nextElementSibling ? 'base' : 'head'
|
||||
}
|
||||
|
||||
return 'head'
|
||||
@ -161,8 +161,8 @@ export const diffDomFunctions: DOMFunctions = {
|
||||
)
|
||||
|
||||
// Some versions of GitHub have data-code-marker attributes instead of the first character diff indicator.
|
||||
const tr = codeElement.closest('tr')
|
||||
const hasDataCodeMarkerUnified = tr?.querySelector('td[data-code-marker]')
|
||||
const tableRow = codeElement.closest('tr')
|
||||
const hasDataCodeMarkerUnified = tableRow?.querySelector('td[data-code-marker]')
|
||||
const hasDataCodeMarkerSplit = blobCodeInner?.hasAttribute('data-code-marker')
|
||||
const hasDataCodeMarker = hasDataCodeMarkerUnified || hasDataCodeMarkerSplit
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { FileInfo } from '../shared/codeHost'
|
||||
import { getCommitIDFromPermalink } from './scrape'
|
||||
import { getDiffFileName, getDiffResolvedRev, getFilePath, parseURL } from './util'
|
||||
import { getDiffFileName, getDiffResolvedRevision, getFilePath, parseURL } from './util'
|
||||
|
||||
export const resolveDiffFileInfo = (codeView: HTMLElement): FileInfo => {
|
||||
const { rawRepoName } = parseURL()
|
||||
@ -8,20 +8,20 @@ export const resolveDiffFileInfo = (codeView: HTMLElement): FileInfo => {
|
||||
if (!headFilePath) {
|
||||
throw new Error('cannot determine file path')
|
||||
}
|
||||
const diffResolvedRev = getDiffResolvedRev(codeView)
|
||||
if (!diffResolvedRev) {
|
||||
const diffResolvedRevision = getDiffResolvedRevision(codeView)
|
||||
if (!diffResolvedRevision) {
|
||||
throw new Error('cannot determine delta info')
|
||||
}
|
||||
const { headCommitID, baseCommitID } = diffResolvedRev
|
||||
const { headCommitID, baseCommitID } = diffResolvedRevision
|
||||
return {
|
||||
rawRepoName,
|
||||
filePath: headFilePath,
|
||||
commitID: headCommitID,
|
||||
rev: headCommitID,
|
||||
revision: headCommitID,
|
||||
baseRawRepoName: rawRepoName,
|
||||
baseFilePath,
|
||||
baseCommitID,
|
||||
baseRev: baseCommitID,
|
||||
baseRevision: baseCommitID,
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,20 +30,20 @@ export const resolveFileInfo = (): FileInfo => {
|
||||
if (parsedURL.pageType !== 'blob' && parsedURL.pageType !== 'tree') {
|
||||
throw new Error(`Current URL does not match a blob or tree url: ${window.location.href}`)
|
||||
}
|
||||
const { revAndFilePath, rawRepoName } = parsedURL
|
||||
const { revisionAndFilePath, rawRepoName } = parsedURL
|
||||
|
||||
const filePath = getFilePath()
|
||||
const filePathWithLeadingSlash = filePath.startsWith('/') ? filePath : `/${filePath}`
|
||||
if (!revAndFilePath.endsWith(filePathWithLeadingSlash)) {
|
||||
if (!revisionAndFilePath.endsWith(filePathWithLeadingSlash)) {
|
||||
throw new Error(
|
||||
`The file path ${filePathWithLeadingSlash} should always be a suffix of revAndFilePath ${revAndFilePath}, but isn't in this case.`
|
||||
`The file path ${filePathWithLeadingSlash} should always be a suffix of revAndFilePath ${revisionAndFilePath}, but isn't in this case.`
|
||||
)
|
||||
}
|
||||
return {
|
||||
rawRepoName,
|
||||
filePath,
|
||||
commitID: getCommitIDFromPermalink(),
|
||||
rev: revAndFilePath.slice(0, -filePathWithLeadingSlash.length),
|
||||
revision: revisionAndFilePath.slice(0, -filePathWithLeadingSlash.length),
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,17 +70,17 @@ export const resolveSnippetFileInfo = (codeView: HTMLElement): FileInfo => {
|
||||
if (parsedURL.pageType !== 'blob') {
|
||||
throw new Error(`Snippet URL does not match a blob url: ${snippetPermalinkURL.href}`)
|
||||
}
|
||||
const { revAndFilePath, rawRepoName } = parsedURL
|
||||
if (!revAndFilePath.startsWith(commitID)) {
|
||||
const { revisionAndFilePath, rawRepoName } = parsedURL
|
||||
if (!revisionAndFilePath.startsWith(commitID)) {
|
||||
throw new Error(
|
||||
`Could not parse filePath: revAndFilePath ${revAndFilePath} does not start with commitID ${commitID}`
|
||||
`Could not parse filePath: revAndFilePath ${revisionAndFilePath} does not start with commitID ${commitID}`
|
||||
)
|
||||
}
|
||||
const filePath = revAndFilePath.slice(commitID.length + 1)
|
||||
const filePath = revisionAndFilePath.slice(commitID.length + 1)
|
||||
return {
|
||||
rawRepoName,
|
||||
filePath,
|
||||
commitID,
|
||||
rev: commitID,
|
||||
revision: commitID,
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ function getPathNamesFromElement(element: HTMLElement): { headFilePath: string;
|
||||
/**
|
||||
* getDiffResolvedRev returns the base and head revision SHA, or null for non-diff views.
|
||||
*/
|
||||
export function getDiffResolvedRev(codeView: HTMLElement): DiffResolvedRevSpec | null {
|
||||
export function getDiffResolvedRevision(codeView: HTMLElement): DiffResolvedRevSpec | null {
|
||||
const { pageType } = parseURL()
|
||||
if (!isDiffPageType(pageType)) {
|
||||
return null
|
||||
@ -66,9 +66,9 @@ export function getDiffResolvedRev(codeView: HTMLElement): DiffResolvedRevSpec |
|
||||
const isCommentedSnippet = codeView.classList.contains('js-comment-container')
|
||||
if (pageType === 'pull') {
|
||||
if (fetchContainers && fetchContainers.length === 1) {
|
||||
for (const el of fetchContainers) {
|
||||
for (const element of fetchContainers) {
|
||||
// for conversation view of pull request
|
||||
const url = el.getAttribute('data-url')
|
||||
const url = element.getAttribute('data-url')
|
||||
if (!url) {
|
||||
continue
|
||||
}
|
||||
@ -96,14 +96,14 @@ export function getDiffResolvedRev(codeView: HTMLElement): DiffResolvedRevSpec |
|
||||
// Refined GitHub adds a `.patch-diff-links` element
|
||||
const shaContainers = document.querySelectorAll('.sha-block:not(.patch-diff-links)')
|
||||
if (shaContainers && shaContainers.length === 2) {
|
||||
const baseShaEl = shaContainers[0].querySelector('a')
|
||||
if (baseShaEl) {
|
||||
const baseShaElement = shaContainers[0].querySelector('a')
|
||||
if (baseShaElement) {
|
||||
// e.g "https://github.com/gorilla/mux/commit/0b13a922203ebdbfd236c818efcd5ed46097d690"
|
||||
baseCommitID = baseShaEl.href.split('/').slice(-1)[0]
|
||||
baseCommitID = baseShaElement.href.split('/').slice(-1)[0]
|
||||
}
|
||||
const headShaEl = shaContainers[1].querySelector('span.sha') as HTMLElement
|
||||
if (headShaEl) {
|
||||
headCommitID = headShaEl.innerHTML
|
||||
const headShaElement = shaContainers[1].querySelector('span.sha') as HTMLElement
|
||||
if (headShaElement) {
|
||||
headCommitID = headShaElement.innerHTML
|
||||
}
|
||||
}
|
||||
} else if (pageType === 'compare') {
|
||||
@ -114,7 +114,7 @@ export function getDiffResolvedRev(codeView: HTMLElement): DiffResolvedRevSpec |
|
||||
}
|
||||
|
||||
if (baseCommitID === '' || headCommitID === '') {
|
||||
return getDiffResolvedRevFromPageSource(document.documentElement.innerHTML, pageType === 'pull')
|
||||
return getDiffResolvedRevisionFromPageSource(document.documentElement.innerHTML, pageType === 'pull')
|
||||
}
|
||||
return { baseCommitID, headCommitID }
|
||||
}
|
||||
@ -137,10 +137,13 @@ function getResolvedDiffFromCommentedSnippet(codeView: HTMLElement): DiffResolve
|
||||
}
|
||||
const headCommitID = match[3]
|
||||
// The file header may not contain the base commit ID, so we get it from the page source.
|
||||
const resolvedRevFromPageSource = getDiffResolvedRevFromPageSource(document.documentElement.innerHTML, true)
|
||||
return headCommitID && resolvedRevFromPageSource
|
||||
const resolvedRevisionFromPageSource = getDiffResolvedRevisionFromPageSource(
|
||||
document.documentElement.innerHTML,
|
||||
true
|
||||
)
|
||||
return headCommitID && resolvedRevisionFromPageSource
|
||||
? {
|
||||
...resolvedRevFromPageSource,
|
||||
...resolvedRevisionFromPageSource,
|
||||
headCommitID,
|
||||
}
|
||||
: null
|
||||
@ -157,7 +160,7 @@ function getResolvedDiffForCompare(): DiffResolvedRevSpec | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getDiffResolvedRevFromPageSource(pageSource: string, isPullRequest: boolean): DiffResolvedRevSpec | null {
|
||||
function getDiffResolvedRevisionFromPageSource(pageSource: string, isPullRequest: boolean): DiffResolvedRevSpec | null {
|
||||
if (!isPullRequest) {
|
||||
return null
|
||||
}
|
||||
@ -220,8 +223,8 @@ type GitHubURL = RawRepoSpec &
|
||||
| { pageType: 'commit' | 'pull' | 'compare' | 'other' }
|
||||
| {
|
||||
pageType: 'blob' | 'tree'
|
||||
/** rev and file path separated by a slash, URL-decoded. */
|
||||
revAndFilePath: string
|
||||
/** revision and file path separated by a slash, URL-decoded. */
|
||||
revisionAndFilePath: string
|
||||
}
|
||||
)
|
||||
|
||||
@ -236,11 +239,11 @@ export function isDiffPageType(pageType: GitHubURL['pageType']): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function parseURL(loc: Pick<Location, 'host' | 'pathname'> = window.location): GitHubURL {
|
||||
const { host, pathname } = loc
|
||||
export function parseURL(location: Pick<Location, 'host' | 'pathname' | 'href'> = window.location): GitHubURL {
|
||||
const { host, pathname } = location
|
||||
const [user, ghRepoName, pageType, ...rest] = pathname.slice(1).split('/')
|
||||
if (!user || !ghRepoName) {
|
||||
throw new Error(`Could not parse repoName from GitHub url: ${window.location.href}`)
|
||||
throw new Error(`Could not parse repoName from GitHub url: ${location.href}`)
|
||||
}
|
||||
const rawRepoName = `${host}/${user}/${ghRepoName}`
|
||||
switch (pageType) {
|
||||
@ -249,7 +252,7 @@ export function parseURL(loc: Pick<Location, 'host' | 'pathname'> = window.locat
|
||||
return {
|
||||
pageType,
|
||||
rawRepoName,
|
||||
revAndFilePath: decodeURIComponent(rest.join('/')),
|
||||
revisionAndFilePath: decodeURIComponent(rest.join('/')),
|
||||
}
|
||||
case 'pull':
|
||||
case 'commit':
|
||||
|
||||
@ -26,7 +26,7 @@ describe('gitlab/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/sourcegraph',
|
||||
rawRepoName: 'gitlab.com/sourcegraph/sourcegraph',
|
||||
rev: 'master',
|
||||
revision: 'master',
|
||||
filePath: 'browser/src/shared/code-hosts/code_intelligence.tsx',
|
||||
position: {
|
||||
line: 5,
|
||||
@ -48,7 +48,7 @@ describe('gitlab/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/sourcegraph',
|
||||
rawRepoName: 'gitlab.sgdev.org/sourcegraph/sourcegraph',
|
||||
rev: 'master',
|
||||
revision: 'master',
|
||||
filePath: 'browser/src/shared/code-hosts/code_intelligence.tsx',
|
||||
position: {
|
||||
line: 5,
|
||||
@ -68,7 +68,7 @@ describe('gitlab/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/sourcegraph',
|
||||
rawRepoName: 'gitlab.com/sourcegraph/sourcegraph',
|
||||
rev: 'master',
|
||||
revision: 'master',
|
||||
filePath: 'browser/src/shared/code-hosts/code_intelligence.tsx',
|
||||
position: {
|
||||
line: 5,
|
||||
@ -88,7 +88,7 @@ describe('gitlab/codeHost', () => {
|
||||
{
|
||||
repoName: 'sourcegraph/jsonrpc2',
|
||||
rawRepoName: 'gitlab.com/sourcegraph/jsonrpc2',
|
||||
rev: 'changes',
|
||||
revision: 'changes',
|
||||
filePath: 'call_opt.go',
|
||||
position: {
|
||||
line: 5,
|
||||
|
||||
@ -7,7 +7,7 @@ import { diffDOMFunctions, singleFileDOMFunctions } from './domFunctions'
|
||||
import { getCommandPaletteMount } from './extensions'
|
||||
import { resolveCommitFileInfo, resolveDiffFileInfo, resolveFileInfo } from './fileInfo'
|
||||
import { getPageInfo, GitLabPageKind, getFilePathsFromCodeView } from './scrape'
|
||||
import { subTypeOf } from '../../../../../shared/src/util/types'
|
||||
import { subtypeOf } from '../../../../../shared/src/util/types'
|
||||
import { NotificationType } from '../../../../../shared/src/api/client/services/notifications'
|
||||
import { toAbsoluteBlobURL } from '../../../../../shared/src/util/url'
|
||||
|
||||
@ -132,7 +132,7 @@ const notificationClassNames = {
|
||||
[NotificationType.Error]: 'alert alert-danger',
|
||||
}
|
||||
|
||||
export const gitlabCodeHost = subTypeOf<CodeHost>()({
|
||||
export const gitlabCodeHost = subtypeOf<CodeHost>()({
|
||||
type: 'gitlab',
|
||||
name: 'GitLab',
|
||||
check: checkIsGitlab,
|
||||
@ -152,7 +152,7 @@ export const gitlabCodeHost = subTypeOf<CodeHost>()({
|
||||
}
|
||||
|
||||
// Stay on same page in MR if possible.
|
||||
// TODO to be entirely correct, this would need to compare the rev of the code view with the target rev.
|
||||
// TODO to be entirely correct, this would need to compare the revision of the code view with the target revision.
|
||||
const currentPage = getPageInfo()
|
||||
if (currentPage.rawRepoName === target.rawRepoName && context.part !== undefined) {
|
||||
const codeViews = document.querySelectorAll<HTMLElement>(codeViewResolver.selector)
|
||||
@ -178,7 +178,7 @@ export const gitlabCodeHost = subTypeOf<CodeHost>()({
|
||||
}
|
||||
|
||||
// Go to specific URL on this Gitlab instance.
|
||||
const url = new URL(`https://${target.rawRepoName}/blob/${target.rev}/${target.filePath}`)
|
||||
const url = new URL(`https://${target.rawRepoName}/blob/${target.revision}/${target.filePath}`)
|
||||
if (target.position) {
|
||||
const { line } = target.position
|
||||
url.hash = `#L${line}`
|
||||
|
||||
@ -2,16 +2,16 @@ import { querySelectorOrSelf } from '../../util/dom'
|
||||
import { MountGetter } from '../shared/codeHost'
|
||||
|
||||
export const getCommandPaletteMount: MountGetter = (container: HTMLElement): HTMLElement | null => {
|
||||
const headerElem = querySelectorOrSelf(container, '.navbar-collapse')
|
||||
if (!headerElem) {
|
||||
const headerElement = querySelectorOrSelf(container, '.navbar-collapse')
|
||||
if (!headerElement) {
|
||||
return null
|
||||
}
|
||||
const commandListClass = 'command-palette-button'
|
||||
const createCommandList = (): HTMLElement => {
|
||||
const mount = document.createElement('div')
|
||||
mount.className = commandListClass
|
||||
headerElem.prepend(mount)
|
||||
headerElement.prepend(mount)
|
||||
return mount
|
||||
}
|
||||
return headerElem.querySelector<HTMLElement>('.' + commandListClass) || createCommandList()
|
||||
return headerElement.querySelector<HTMLElement>('.' + commandListClass) || createCommandList()
|
||||
}
|
||||
|
||||
@ -19,14 +19,14 @@ import { asObservable } from '../../../../../shared/src/util/rxjs/asObservable'
|
||||
* Resolves file information for a page with a single file, not including diffs with only one file.
|
||||
*/
|
||||
export const resolveFileInfo = (): FileInfo => {
|
||||
const { rawRepoName, filePath, rev } = getFilePageInfo()
|
||||
const { rawRepoName, filePath, revision } = getFilePageInfo()
|
||||
if (!filePath) {
|
||||
throw new Error(
|
||||
`Unable to determine the file path of the current file because the current URL (window.location ${window.location.href}) does not have a file path.`
|
||||
)
|
||||
}
|
||||
const commitID = getCommitIDFromPermalink()
|
||||
return { rawRepoName, filePath, commitID, rev }
|
||||
return { rawRepoName, filePath, commitID, revision }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { last, take } from 'lodash'
|
||||
|
||||
import { FileSpec, RawRepoSpec, RevSpec } from '../../../../../shared/src/util/url'
|
||||
import { FileSpec, RawRepoSpec, RevisionSpec } from '../../../../../shared/src/util/url'
|
||||
import { commitIDFromPermalink } from '../../util/dom'
|
||||
import { FileInfo } from '../shared/codeHost'
|
||||
import { isExtension } from '../../context'
|
||||
@ -25,7 +25,7 @@ export interface GitLabInfo extends RawRepoSpec {
|
||||
/**
|
||||
* Information about single file pages.
|
||||
*/
|
||||
interface GitLabFileInfo extends RawRepoSpec, FileSpec, RevSpec {}
|
||||
interface GitLabFileInfo extends RawRepoSpec, FileSpec, RevisionSpec {}
|
||||
|
||||
export const getPageKindFromPathName = (owner: string, projectName: string, pathname: string): GitLabPageKind => {
|
||||
const pageKindMatch = pathname.match(new RegExp(`^/${owner}/${projectName}(/-)?/(commit|merge_requests|blob)/`))
|
||||
@ -81,12 +81,12 @@ export function getFilePageInfo(): GitLabFileInfo {
|
||||
throw new Error('Unable to determine revision or file path')
|
||||
}
|
||||
|
||||
const rev = decodeURIComponent(matches[1])
|
||||
const revision = decodeURIComponent(matches[1])
|
||||
const filePath = decodeURIComponent(matches[2])
|
||||
return {
|
||||
rawRepoName,
|
||||
filePath,
|
||||
rev,
|
||||
revision,
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,12 +106,12 @@ export const getMergeRequestID = (): string => {
|
||||
* The diff ID represents a specific revision in a merge request.
|
||||
*/
|
||||
export const getDiffID = (): string | undefined => {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
return params.get('diff_id') ?? undefined
|
||||
const parameters = new URLSearchParams(window.location.search)
|
||||
return parameters.get('diff_id') ?? undefined
|
||||
}
|
||||
|
||||
const getFilePathFromElem = (elem: HTMLElement): string => {
|
||||
const filePath = elem.dataset.originalTitle || elem.dataset.title || elem.title
|
||||
const getFilePathFromElement = (element: HTMLElement): string => {
|
||||
const filePath = element.dataset.originalTitle || element.dataset.title || element.title
|
||||
if (!filePath) {
|
||||
throw new Error('Unable to get file paths from code view: no file title')
|
||||
}
|
||||
@ -129,11 +129,11 @@ export function getFilePathsFromCodeView(codeView: HTMLElement): Pick<FileInfo,
|
||||
}
|
||||
|
||||
const filePathDidChange = filePathElements.length > 1
|
||||
const filePath = getFilePathFromElem(filePathElements.item(filePathDidChange ? 1 : 0))
|
||||
const filePath = getFilePathFromElement(filePathElements.item(filePathDidChange ? 1 : 0))
|
||||
|
||||
return {
|
||||
filePath,
|
||||
baseFilePath: filePathDidChange ? getFilePathFromElem(filePathElements.item(0)) : filePath,
|
||||
baseFilePath: filePathDidChange ? getFilePathFromElement(filePathElements.item(0)) : filePath,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import { isExtension } from '../../context'
|
||||
import { resolveRepo } from '../../repo/backend'
|
||||
import { normalizeRepoName } from './util'
|
||||
import { isRepoNotFoundErrorLike } from '../../../../../shared/src/backend/errors'
|
||||
import { RepoSpec, FileSpec, ResolvedRevSpec } from '../../../../../shared/src/util/url'
|
||||
import { RepoSpec, FileSpec, ResolvedRevisionSpec } from '../../../../../shared/src/util/url'
|
||||
import { RevisionSpec, DiffSpec, BaseDiffSpec } from '.'
|
||||
import { checkOk } from '../../../../../shared/src/backend/fetch'
|
||||
import { fromFetch } from '../../../../../shared/src/graphql/fromFetch'
|
||||
@ -124,9 +124,9 @@ export type QueryConduitHelper<T> = (endpoint: string, params: {}) => Observable
|
||||
/**
|
||||
* Generic helper to query the Phabricator Conduit API.
|
||||
*/
|
||||
export function queryConduitHelper<T>(endpoint: string, params: {}): Observable<T> {
|
||||
export function queryConduitHelper<T>(endpoint: string, parameters: {}): Observable<T> {
|
||||
const form = createConduitRequestForm()
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
for (const [key, value] of Object.entries(parameters)) {
|
||||
form.set(`params[${key}]`, JSON.stringify(value))
|
||||
}
|
||||
return fromFetch(
|
||||
@ -410,7 +410,10 @@ interface ResolveStagingOptions extends Pick<PlatformContext, 'requestGraphQL'>,
|
||||
* Returns the commit ID of the one-off commit created on the Sourcegraph instance for the given
|
||||
* repo/diffID/patch, creating that commit if needed.
|
||||
*/
|
||||
const resolveStagingRev = ({ requestGraphQL, ...variables }: ResolveStagingOptions): Observable<ResolvedRevSpec> =>
|
||||
const resolveStagingRevision = ({
|
||||
requestGraphQL,
|
||||
...variables
|
||||
}: ResolveStagingOptions): Observable<ResolvedRevisionSpec> =>
|
||||
requestGraphQL<GQL.IMutation>({
|
||||
request: gql`
|
||||
mutation ResolveStagingRev(
|
||||
@ -447,7 +450,7 @@ const resolveStagingRev = ({ requestGraphQL, ...variables }: ResolveStagingOptio
|
||||
}
|
||||
const { oid } = resolvePhabricatorDiff
|
||||
if (!oid) {
|
||||
throw new Error('Could not resolve staging rev: empty oid')
|
||||
throw new Error('Could not resolve staging revision: empty oid')
|
||||
}
|
||||
return { commitID: oid }
|
||||
})
|
||||
@ -514,13 +517,13 @@ function getStagingDetails(
|
||||
const type = propsWithInfo.useBaseForDiff ? 'base' : 'diff'
|
||||
key = `refs/tags/phabricator/${type}/${propsWithInfo.diffID}`
|
||||
}
|
||||
for (const ref of propsWithInfo.diffDetails.properties['arc.staging'].refs) {
|
||||
if (ref.ref === key) {
|
||||
const remote = ref.remote.uri
|
||||
for (const reference of propsWithInfo.diffDetails.properties['arc.staging'].refs) {
|
||||
if (reference.ref === key) {
|
||||
const remote = reference.remote.uri
|
||||
if (remote) {
|
||||
return {
|
||||
repoName: normalizeRepoName(remote),
|
||||
ref,
|
||||
ref: reference,
|
||||
unconfigured: stagingInfo.status === 'repository.unconfigured',
|
||||
}
|
||||
}
|
||||
@ -529,7 +532,7 @@ function getStagingDetails(
|
||||
return undefined
|
||||
}
|
||||
|
||||
interface ResolvedDiff extends ResolvedRevSpec {
|
||||
interface ResolvedDiff extends ResolvedRevisionSpec {
|
||||
/**
|
||||
* The name of the staging repository, if it is synced to the Sourcegraph instance.
|
||||
*/
|
||||
@ -551,7 +554,7 @@ interface ResolvedDiff extends ResolvedRevSpec {
|
||||
* be returned ({@see resolveStagingRev}).
|
||||
*
|
||||
*/
|
||||
export function resolveDiffRev(
|
||||
export function resolveDiffRevision(
|
||||
props: ResolveDiffOpt,
|
||||
requestGraphQL: PlatformContext['requestGraphQL'],
|
||||
queryConduit: QueryConduitHelper<any>
|
||||
@ -580,7 +583,7 @@ export function resolveDiffRev(
|
||||
// create a one-off commit on the Sourcegraph instance from the patch,
|
||||
// and resolve to the commit ID returned by the Sourcegraph instance.
|
||||
return getRawDiffFromConduit({ diffID: props.diffID, queryConduit }).pipe(
|
||||
switchMap(patch => resolveStagingRev({ ...conduitProps, patch, requestGraphQL }))
|
||||
switchMap(patch => resolveStagingRevision({ ...conduitProps, patch, requestGraphQL }))
|
||||
)
|
||||
}
|
||||
|
||||
@ -599,7 +602,7 @@ export function resolveDiffRev(
|
||||
throw error
|
||||
}
|
||||
return getRawDiffFromConduit({ diffID: props.diffID, queryConduit }).pipe(
|
||||
switchMap(patch => resolveStagingRev({ ...conduitProps, patch, requestGraphQL }))
|
||||
switchMap(patch => resolveStagingRevision({ ...conduitProps, patch, requestGraphQL }))
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@ -2,7 +2,7 @@ import { AdjustmentDirection, PositionAdjuster } from '@sourcegraph/codeintellif
|
||||
import { Position } from '@sourcegraph/extension-api-types'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { PlatformContext } from '../../../../../shared/src/platform/context'
|
||||
import { FileSpec, RepoSpec, ResolvedRevSpec, RevSpec } from '../../../../../shared/src/util/url'
|
||||
import { FileSpec, RepoSpec, ResolvedRevisionSpec, RevisionSpec } from '../../../../../shared/src/util/url'
|
||||
import { fetchBlobContentLines } from '../../repo/backend'
|
||||
import { CodeHost } from '../shared/codeHost'
|
||||
import { CodeView, toCodeViewResolver } from '../shared/codeViews'
|
||||
@ -48,7 +48,7 @@ const adjustCharacter = (position: Position, adjustment: number): Position => ({
|
||||
|
||||
const getPositionAdjuster = (
|
||||
requestGraphQL: PlatformContext['requestGraphQL']
|
||||
): PositionAdjuster<RepoSpec & RevSpec & FileSpec & ResolvedRevSpec> => ({ direction, codeView, position }) =>
|
||||
): PositionAdjuster<RepoSpec & RevisionSpec & FileSpec & ResolvedRevisionSpec> => ({ direction, codeView, position }) =>
|
||||
fetchBlobContentLines({ ...position, requestGraphQL }).pipe(
|
||||
map(lines => {
|
||||
const codeElement = diffDomFunctions.getCodeElementFromLineNumber(codeView, position.line, position.part)
|
||||
|
||||
@ -21,12 +21,12 @@ const getLineNumber = (lineNumberCell: HTMLElement): number =>
|
||||
* and `<td>` line number cells with a `data-n` attribtue (recent Phabricator versions).
|
||||
*/
|
||||
const getLineNumberCellFromCodeElement = (codeElement: HTMLElement): HTMLElement | null => {
|
||||
let elem: HTMLElement | null = codeElement
|
||||
while (elem) {
|
||||
if (isLineNumberCell(elem)) {
|
||||
return elem
|
||||
let element: HTMLElement | null = codeElement
|
||||
while (element) {
|
||||
if (isLineNumberCell(element)) {
|
||||
return element
|
||||
}
|
||||
elem = elem.previousElementSibling as HTMLElement | null
|
||||
element = element.previousElementSibling as HTMLElement | null
|
||||
}
|
||||
return null
|
||||
}
|
||||
@ -78,20 +78,20 @@ export const diffDomFunctions: DOMFunctions = {
|
||||
return null
|
||||
}
|
||||
|
||||
const td = target.closest('td')
|
||||
if (!td) {
|
||||
const tableCell = target.closest('td')
|
||||
if (!tableCell) {
|
||||
return null
|
||||
}
|
||||
if (td.classList.contains('show-more') || td.classList.contains('show-context')) {
|
||||
if (tableCell.classList.contains('show-more') || tableCell.classList.contains('show-context')) {
|
||||
// This element represents a collapsed part of the diff, it's not a code element.
|
||||
return null
|
||||
}
|
||||
if (!getLineNumberCellFromCodeElement(td)) {
|
||||
if (!getLineNumberCellFromCodeElement(tableCell)) {
|
||||
// The element has no associated line number cell: this can be the case when hovering
|
||||
// 'empty' lines in the base part of a split diff that has added lines.
|
||||
return null
|
||||
}
|
||||
return td
|
||||
return tableCell
|
||||
},
|
||||
getCodeElementFromLineNumber: getDiffCodeElementFromLineNumber,
|
||||
getLineElementFromLineNumber: getDiffCodeElementFromLineNumber,
|
||||
|
||||
@ -44,11 +44,11 @@ const DEFAULT_CONDUIT_RESPONSES: ConduitResponseMap = {
|
||||
repositoryPHID: '1',
|
||||
},
|
||||
}),
|
||||
'/api/differential.querydiffs': (params: { ids: string[]; revisionIDs: string[] }) =>
|
||||
'/api/differential.querydiffs': (parameters: { ids: string[]; revisionIDs: string[] }) =>
|
||||
of({
|
||||
[params.ids[0]]: {
|
||||
id: params.ids[0],
|
||||
revisionID: params.revisionIDs[0],
|
||||
[parameters.ids[0]]: {
|
||||
id: parameters.ids[0],
|
||||
revisionID: parameters.revisionIDs[0],
|
||||
dateCreated: '1566329300',
|
||||
dateModified: '1566329305',
|
||||
sourceControlBaseRevision: 'base-revision',
|
||||
@ -67,15 +67,15 @@ const DEFAULT_CONDUIT_RESPONSES: ConduitResponseMap = {
|
||||
status: 'pushed',
|
||||
refs: [
|
||||
{
|
||||
ref: `refs/tags/phabricator/base/${params.ids[0]}`,
|
||||
ref: `refs/tags/phabricator/base/${parameters.ids[0]}`,
|
||||
type: 'base',
|
||||
commit: `base-${params.ids[0]}`,
|
||||
commit: `base-${parameters.ids[0]}`,
|
||||
remote: { uri: 'https://github.com/lguychard/testing.git' },
|
||||
},
|
||||
{
|
||||
ref: `refs/tags/phabricator/diff/${params.ids[0]}`,
|
||||
ref: `refs/tags/phabricator/diff/${parameters.ids[0]}`,
|
||||
type: 'diff',
|
||||
commit: `diff-${params.ids[0]}`,
|
||||
commit: `diff-${parameters.ids[0]}`,
|
||||
remote: { uri: 'https://github.com/lguychard/testing.git' },
|
||||
},
|
||||
],
|
||||
@ -103,18 +103,18 @@ const DEFAULT_GRAPHQL_RESPONSES: GraphQLResponseMap = {
|
||||
} as SuccessGraphQLResult<IQuery>),
|
||||
ResolveStagingRev: () =>
|
||||
of({
|
||||
data: { resolvePhabricatorDiff: { oid: 'staging-rev' } },
|
||||
data: { resolvePhabricatorDiff: { oid: 'staging-revision' } },
|
||||
errors: undefined,
|
||||
} as SuccessGraphQLResult<IMutation>),
|
||||
}
|
||||
|
||||
function mockQueryConduit(responseMap?: ConduitResponseMap): QueryConduitHelper<any> {
|
||||
return (endpoint, params) => {
|
||||
return (endpoint, parameters) => {
|
||||
const mock = responseMap?.[endpoint] || DEFAULT_CONDUIT_RESPONSES[endpoint]
|
||||
if (!mock) {
|
||||
return throwError(new Error(`No mock for endpoint ${endpoint}`))
|
||||
}
|
||||
return mock(params)
|
||||
return mock(parameters)
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,11 +304,11 @@ describe('Phabricator file info', () => {
|
||||
codeViewSelector: '.differential-changeset',
|
||||
conduitResponseMap: {
|
||||
// Returns diff details without staging details
|
||||
'/api/differential.querydiffs': params =>
|
||||
'/api/differential.querydiffs': parameters =>
|
||||
of({
|
||||
[params.ids[0]]: {
|
||||
id: params.ids[0],
|
||||
revisionID: params.revisionIDs[0],
|
||||
[parameters.ids[0]]: {
|
||||
id: parameters.ids[0],
|
||||
revisionID: parameters.revisionIDs[0],
|
||||
dateCreated: '1566329300',
|
||||
dateModified: '1566329305',
|
||||
sourceControlBaseRevision: 'base-revision',
|
||||
@ -328,7 +328,7 @@ describe('Phabricator file info', () => {
|
||||
baseCommitID: 'base-revision',
|
||||
baseFilePath: 'helpers/add.go',
|
||||
baseRawRepoName: 'github.com/gorilla/mux',
|
||||
commitID: 'staging-rev',
|
||||
commitID: 'staging-revision',
|
||||
filePath: 'helpers/add.go',
|
||||
rawRepoName: 'github.com/gorilla/mux',
|
||||
})
|
||||
@ -347,7 +347,7 @@ describe('Phabricator file info', () => {
|
||||
baseCommitID: 'base-revision',
|
||||
baseFilePath: 'helpers/add.go',
|
||||
baseRawRepoName: 'github.com/gorilla/mux',
|
||||
commitID: 'staging-rev',
|
||||
commitID: 'staging-revision',
|
||||
filePath: 'helpers/add.go',
|
||||
rawRepoName: 'github.com/gorilla/mux',
|
||||
})
|
||||
@ -395,23 +395,25 @@ describe('Phabricator file info', () => {
|
||||
ResolveStagingRev: (variables: any) =>
|
||||
of({
|
||||
data: {
|
||||
resolvePhabricatorDiff: { oid: `staging-rev-${variables.patch as string}` },
|
||||
resolvePhabricatorDiff: {
|
||||
oid: `staging-revision-${variables.patch as string}`,
|
||||
},
|
||||
},
|
||||
errors: undefined,
|
||||
} as SuccessGraphQLResult<IMutation>),
|
||||
},
|
||||
conduitResponseMap: {
|
||||
'/api/differential.getrawdiff': params =>
|
||||
of(`raw-diff-for-diffid-${params.diffID as string}`),
|
||||
'/api/differential.getrawdiff': parameters =>
|
||||
of(`raw-diff-for-diffid-${parameters.diffID as string}`),
|
||||
},
|
||||
},
|
||||
resolveDiffFileInfo
|
||||
)
|
||||
).toEqual({
|
||||
baseCommitID: 'staging-rev-raw-diff-for-diffid-2',
|
||||
baseCommitID: 'staging-revision-raw-diff-for-diffid-2',
|
||||
baseFilePath: '.arcconfig',
|
||||
baseRawRepoName: 'github.com/gorilla/mux',
|
||||
commitID: 'staging-rev-raw-diff-for-diffid-3',
|
||||
commitID: 'staging-revision-raw-diff-for-diffid-3',
|
||||
filePath: '.arcconfig',
|
||||
rawRepoName: 'github.com/gorilla/mux',
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@ import { map, switchMap } from 'rxjs/operators'
|
||||
import { PlatformContext } from '../../../../../shared/src/platform/context'
|
||||
import { FileInfo } from '../shared/codeHost'
|
||||
import { PhabricatorMode } from '.'
|
||||
import { queryConduitHelper, resolveDiffRev } from './backend'
|
||||
import { queryConduitHelper, resolveDiffRevision } from './backend'
|
||||
import { getFilepathFromFileForDiff, getFilePathFromFileForRevision } from './scrape'
|
||||
import { getPhabricatorState } from './util'
|
||||
|
||||
@ -42,7 +42,7 @@ export const resolveDiffFileInfo = (
|
||||
throw new Error(`Unexpected PhabricatorState for resolveDiffFileInfo, PhabricatorMode: ${state.mode}`)
|
||||
}
|
||||
const { filePath, baseFilePath } = getFilepathFromFileForDiff(codeView)
|
||||
const resolveBaseCommitID = resolveDiffRev(
|
||||
const resolveBaseCommitID = resolveDiffRevision(
|
||||
{
|
||||
repoName: state.baseRawRepoName,
|
||||
revisionID: state.revisionID,
|
||||
@ -64,7 +64,7 @@ export const resolveDiffFileInfo = (
|
||||
})
|
||||
)
|
||||
)
|
||||
const resolveHeadCommitID = resolveDiffRev(
|
||||
const resolveHeadCommitID = resolveDiffRevision(
|
||||
{
|
||||
repoName: state.headRawRepoName,
|
||||
revisionID: state.revisionID,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FileSpec, RawRepoSpec, ResolvedRevSpec } from '../../../../../shared/src/util/url'
|
||||
import { FileSpec, RawRepoSpec, ResolvedRevisionSpec } from '../../../../../shared/src/util/url'
|
||||
|
||||
export enum PhabricatorMode {
|
||||
Diffusion = 1,
|
||||
@ -7,7 +7,7 @@ export enum PhabricatorMode {
|
||||
Change,
|
||||
}
|
||||
|
||||
export interface DiffusionState extends RawRepoSpec, ResolvedRevSpec, FileSpec {
|
||||
export interface DiffusionState extends RawRepoSpec, ResolvedRevisionSpec, FileSpec {
|
||||
mode: PhabricatorMode.Diffusion
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ export interface RevisionState extends RawRepoSpec {
|
||||
* Refers to a URL like http://phabricator.aws.sgdev.org/source/nzap/change/master/checked_message_bench_test.go,
|
||||
* which a user gets to by clicking "Show Last Change" on a differential page.
|
||||
*/
|
||||
export interface ChangeState extends RawRepoSpec, FileSpec, ResolvedRevSpec {
|
||||
export interface ChangeState extends RawRepoSpec, FileSpec, ResolvedRevisionSpec {
|
||||
mode: PhabricatorMode.Change
|
||||
}
|
||||
|
||||
|
||||
@ -6,11 +6,11 @@ import { Observable, throwError } from 'rxjs'
|
||||
|
||||
const TAG_PATTERN = /r([\dA-z]+)([\da-f]{40})/
|
||||
function matchPageTag(): RegExpExecArray | null {
|
||||
const el = document.querySelectorAll('.phui-tag-core').item(0)
|
||||
if (!el) {
|
||||
const element = document.querySelectorAll('.phui-tag-core').item(0)
|
||||
if (!element) {
|
||||
throw new Error('Could not find Phabricator page tag')
|
||||
}
|
||||
return TAG_PATTERN.exec(el.children[0].getAttribute('href') as string)
|
||||
return TAG_PATTERN.exec(element.children[0].getAttribute('href') as string)
|
||||
}
|
||||
|
||||
function getCallsignFromPageTag(): string {
|
||||
@ -77,12 +77,12 @@ function getBaseCommitIDFromRevisionPage(): string {
|
||||
}
|
||||
|
||||
export function getPhabricatorState(
|
||||
loc: Location,
|
||||
location: Location,
|
||||
requestGraphQL: PlatformContext['requestGraphQL'],
|
||||
queryConduit: QueryConduitHelper<any>
|
||||
): Observable<DiffusionState | DifferentialState | RevisionState | ChangeState> {
|
||||
try {
|
||||
const stateUrl = loc.href.replace(loc.origin, '')
|
||||
const stateUrl = location.href.replace(location.origin, '')
|
||||
const diffusionMatch = PHAB_DIFFUSION_REGEX.exec(stateUrl)
|
||||
if (diffusionMatch) {
|
||||
const filePath = diffusionMatch[4]
|
||||
|
||||
@ -9,14 +9,14 @@ export class MockIntersectionObserver implements IntersectionObserver {
|
||||
public readonly rootMargin: string
|
||||
public readonly thresholds: readonly number[]
|
||||
|
||||
constructor(private cb: IntersectionObserverCallback, options?: IntersectionObserverInit) {
|
||||
constructor(private callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
|
||||
this.root = options?.root ?? null
|
||||
this.rootMargin = options?.rootMargin ?? '0px 0px 0px 0px'
|
||||
this.thresholds = castArray(options?.threshold)
|
||||
}
|
||||
|
||||
public observe(target: Element): void {
|
||||
this.cb(
|
||||
this.callback(
|
||||
[
|
||||
{
|
||||
target,
|
||||
|
||||
@ -40,7 +40,7 @@ describe('<ViewOnSourcegraphButton />', () => {
|
||||
expect(root!).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders a link with the rev when provided', () => {
|
||||
it('renders a link with the revision when provided', () => {
|
||||
let root: ReactTestRenderer
|
||||
renderer.act(() => {
|
||||
root = renderer.create(
|
||||
@ -49,7 +49,7 @@ describe('<ViewOnSourcegraphButton />', () => {
|
||||
sourcegraphURL="https://test.com"
|
||||
getContext={() => ({
|
||||
rawRepoName: 'test',
|
||||
rev: 'test',
|
||||
revision: 'test',
|
||||
privateRepository: false,
|
||||
})}
|
||||
className="test"
|
||||
@ -72,7 +72,7 @@ describe('<ViewOnSourcegraphButton />', () => {
|
||||
sourcegraphURL="https://sourcegraph.com"
|
||||
getContext={() => ({
|
||||
rawRepoName: 'test',
|
||||
rev: 'test',
|
||||
revision: 'test',
|
||||
privateRepository: false,
|
||||
})}
|
||||
className="test"
|
||||
@ -94,7 +94,7 @@ describe('<ViewOnSourcegraphButton />', () => {
|
||||
sourcegraphURL="https://sourcegraph.test"
|
||||
getContext={() => ({
|
||||
rawRepoName: 'test',
|
||||
rev: 'test',
|
||||
revision: 'test',
|
||||
privateRepository: false,
|
||||
})}
|
||||
className="test"
|
||||
@ -121,7 +121,7 @@ describe('<ViewOnSourcegraphButton />', () => {
|
||||
sourcegraphURL="https://test.com"
|
||||
getContext={() => ({
|
||||
rawRepoName: 'test',
|
||||
rev: 'test',
|
||||
revision: 'test',
|
||||
privateRepository: false,
|
||||
})}
|
||||
showSignInButton={true}
|
||||
@ -147,7 +147,7 @@ describe('<ViewOnSourcegraphButton />', () => {
|
||||
sourcegraphURL="https://test.com"
|
||||
getContext={() => ({
|
||||
rawRepoName: 'test',
|
||||
rev: 'test',
|
||||
revision: 'test',
|
||||
privateRepository: false,
|
||||
})}
|
||||
showSignInButton={true}
|
||||
|
||||
@ -55,8 +55,8 @@ export const ViewOnSourcegraphButton: React.FunctionComponent<ViewOnSourcegraphB
|
||||
return null
|
||||
}
|
||||
|
||||
const { rawRepoName, rev } = getContext()
|
||||
const url = new URL(`/${rawRepoName}${rev ? `@${rev}` : ''}`, sourcegraphURL).href
|
||||
const { rawRepoName, revision } = getContext()
|
||||
const url = new URL(`/${rawRepoName}${revision ? `@${revision}` : ''}`, sourcegraphURL).href
|
||||
|
||||
if (isErrorLike(repoExistsOrError)) {
|
||||
// If the problem is the user is not signed in, show a sign in CTA (if not shown elsewhere)
|
||||
|
||||
@ -210,7 +210,7 @@ exports[`<ViewOnSourcegraphButton /> repository exists on the instance renders a
|
||||
</a>
|
||||
`;
|
||||
|
||||
exports[`<ViewOnSourcegraphButton /> repository exists on the instance renders a link with the rev when provided 1`] = `
|
||||
exports[`<ViewOnSourcegraphButton /> repository exists on the instance renders a link with the revision when provided 1`] = `
|
||||
<a
|
||||
aria-label="View repository on Sourcegraph"
|
||||
className="open-on-sourcegraph test"
|
||||
|
||||
@ -14,7 +14,7 @@ import { SuccessGraphQLResult } from '../../../../../shared/src/graphql/graphql'
|
||||
import { IQuery } from '../../../../../shared/src/graphql/schema'
|
||||
import { NOOP_TELEMETRY_SERVICE } from '../../../../../shared/src/telemetry/telemetryService'
|
||||
import { resetAllMemoizationCaches } from '../../../../../shared/src/util/memoizeObservable'
|
||||
import { isDefined, subTypeOf, allOf, check, isTaggedUnionMember } from '../../../../../shared/src/util/types'
|
||||
import { isDefined, subtypeOf, allOf, check, isTaggedUnionMember } from '../../../../../shared/src/util/types'
|
||||
import { DEFAULT_SOURCEGRAPH_URL } from '../../util/context'
|
||||
import { MutationRecordLike } from '../../util/dom'
|
||||
import {
|
||||
@ -51,10 +51,10 @@ const elementRenderedAtMount = (mount: Element): renderer.ReactTestRendererJSON
|
||||
const scheduler = (): TestScheduler => new TestScheduler((a, b) => expect(a).toEqual(b))
|
||||
|
||||
const createTestElement = (): HTMLElement => {
|
||||
const el = document.createElement('div')
|
||||
el.className = `test test-${uniqueId()}`
|
||||
document.body.append(el)
|
||||
return el
|
||||
const element = document.createElement('div')
|
||||
element.className = `test test-${uniqueId()}`
|
||||
document.body.append(element)
|
||||
return element
|
||||
}
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
@ -80,8 +80,8 @@ const createMockPlatformContext = (
|
||||
...partialMocks,
|
||||
})
|
||||
|
||||
const commonArgs = () =>
|
||||
subTypeOf<Partial<HandleCodeHostOptions>>()({
|
||||
const commonArguments = () =>
|
||||
subtypeOf<Partial<HandleCodeHostOptions>>()({
|
||||
mutations: of([{ addedNodes: [document.body], removedNodes: [] }]),
|
||||
showGlobalDebug: false,
|
||||
platformContext: createMockPlatformContext(),
|
||||
@ -134,7 +134,7 @@ describe('codeHost', () => {
|
||||
const { services } = await integrationTestContext()
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -157,7 +157,7 @@ describe('codeHost', () => {
|
||||
const commandPaletteMount = createTestElement()
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -177,7 +177,7 @@ describe('codeHost', () => {
|
||||
const { services } = await integrationTestContext()
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -208,7 +208,7 @@ describe('codeHost', () => {
|
||||
}
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -281,7 +281,7 @@ describe('codeHost', () => {
|
||||
codeView.append(line)
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -395,7 +395,7 @@ describe('codeHost', () => {
|
||||
}
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -435,7 +435,7 @@ describe('codeHost', () => {
|
||||
const baseEditor = viewers.find(
|
||||
allOf(
|
||||
isTaggedUnionMember('type', 'CodeEditor' as const),
|
||||
check(e => e.document.uri === 'git://foo?1#/bar.ts')
|
||||
check(editor => editor.document.uri === 'git://foo?1#/bar.ts')
|
||||
)
|
||||
)!
|
||||
const baseDecorations = [
|
||||
@ -469,7 +469,7 @@ describe('codeHost', () => {
|
||||
const headEditor = viewers.find(
|
||||
allOf(
|
||||
isTaggedUnionMember('type', 'CodeEditor' as const),
|
||||
check(e => e.document.uri === 'git://foo?2#/bar.ts')
|
||||
check(editor => editor.document.uri === 'git://foo?2#/bar.ts')
|
||||
)
|
||||
)!
|
||||
const headDecorations = [
|
||||
@ -545,7 +545,7 @@ describe('codeHost', () => {
|
||||
])
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
mutations,
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
@ -624,7 +624,7 @@ describe('codeHost', () => {
|
||||
}
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -667,7 +667,7 @@ describe('codeHost', () => {
|
||||
}
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -724,7 +724,7 @@ describe('codeHost', () => {
|
||||
}
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -775,7 +775,7 @@ describe('codeHost', () => {
|
||||
}
|
||||
subscriptions.add(
|
||||
handleCodeHost({
|
||||
...commonArgs(),
|
||||
...commonArguments(),
|
||||
codeHost: {
|
||||
type: 'github',
|
||||
name: 'GitHub',
|
||||
@ -843,7 +843,7 @@ describe('codeHost', () => {
|
||||
})
|
||||
|
||||
test('emits a custom mount location if a node matching the selector is in addedNodes()', () => {
|
||||
const el = createTestElement()
|
||||
const element = createTestElement()
|
||||
scheduler().run(({ cold, expectObservable }) => {
|
||||
expectObservable(
|
||||
observeHoverOverlayMountLocation(
|
||||
@ -851,7 +851,7 @@ describe('codeHost', () => {
|
||||
cold<MutationRecordLike[]>('-b', {
|
||||
b: [
|
||||
{
|
||||
addedNodes: [el],
|
||||
addedNodes: [element],
|
||||
removedNodes: [],
|
||||
},
|
||||
],
|
||||
@ -859,16 +859,16 @@ describe('codeHost', () => {
|
||||
)
|
||||
).toBe('ab', {
|
||||
a: document.body,
|
||||
b: el,
|
||||
b: element,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('emits a custom mount location if a node matching the selector is nested in an addedNode', () => {
|
||||
const el = createTestElement()
|
||||
const element = createTestElement()
|
||||
const nested = document.createElement('div')
|
||||
nested.classList.add('nested')
|
||||
el.append(nested)
|
||||
element.append(nested)
|
||||
scheduler().run(({ cold, expectObservable }) => {
|
||||
expectObservable(
|
||||
observeHoverOverlayMountLocation(
|
||||
@ -876,7 +876,7 @@ describe('codeHost', () => {
|
||||
cold<MutationRecordLike[]>('-b', {
|
||||
b: [
|
||||
{
|
||||
addedNodes: [el],
|
||||
addedNodes: [element],
|
||||
removedNodes: [],
|
||||
},
|
||||
],
|
||||
@ -890,7 +890,7 @@ describe('codeHost', () => {
|
||||
})
|
||||
|
||||
test('emits document.body if a node matching the selector is removed', () => {
|
||||
const el = createTestElement()
|
||||
const element = createTestElement()
|
||||
scheduler().run(({ cold, expectObservable }) => {
|
||||
expectObservable(
|
||||
observeHoverOverlayMountLocation(
|
||||
@ -898,21 +898,21 @@ describe('codeHost', () => {
|
||||
cold<MutationRecordLike[]>('-bc', {
|
||||
b: [
|
||||
{
|
||||
addedNodes: [el],
|
||||
addedNodes: [element],
|
||||
removedNodes: [],
|
||||
},
|
||||
],
|
||||
c: [
|
||||
{
|
||||
addedNodes: [],
|
||||
removedNodes: [el],
|
||||
removedNodes: [element],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
).toBe('abc', {
|
||||
a: document.body,
|
||||
b: el,
|
||||
b: element,
|
||||
c: document.body,
|
||||
})
|
||||
})
|
||||
|
||||
@ -73,8 +73,8 @@ import {
|
||||
UIPositionSpec,
|
||||
RawRepoSpec,
|
||||
RepoSpec,
|
||||
ResolvedRevSpec,
|
||||
RevSpec,
|
||||
ResolvedRevisionSpec,
|
||||
RevisionSpec,
|
||||
toRootURI,
|
||||
toURIWithPath,
|
||||
ViewStateSpec,
|
||||
@ -82,9 +82,9 @@ import {
|
||||
import { observeStorageKey } from '../../../browser-extension/web-extension-api/storage'
|
||||
import { isInPage } from '../../context'
|
||||
import { SourcegraphIntegrationURLs, BrowserPlatformContext } from '../../platform/context'
|
||||
import { toTextDocumentIdentifier, toTextDocumentPositionParams } from '../../backend/ext-api-conversion'
|
||||
import { toTextDocumentIdentifier, toTextDocumentPositionParameters } from '../../backend/extension-api-conversion'
|
||||
import { CodeViewToolbar, CodeViewToolbarClassProps } from '../../components/CodeViewToolbar'
|
||||
import { resolveRev, retryWhenCloneInProgressError } from '../../repo/backend'
|
||||
import { resolveRevision, retryWhenCloneInProgressError } from '../../repo/backend'
|
||||
import { EventLogger } from '../../tracking/eventLogger'
|
||||
import { MutationRecordLike, querySelectorOrSelf } from '../../util/dom'
|
||||
import { featureFlags } from '../../util/featureFlags'
|
||||
@ -135,7 +135,7 @@ export type MountGetter = (container: HTMLElement) => HTMLElement | null
|
||||
/**
|
||||
* The context the code host is in on the current page.
|
||||
*/
|
||||
export type CodeHostContext = RawRepoSpec & Partial<RevSpec> & { privateRepository: boolean }
|
||||
export type CodeHostContext = RawRepoSpec & Partial<RevisionSpec> & { privateRepository: boolean }
|
||||
|
||||
type CodeHostType = 'github' | 'phabricator' | 'bitbucket-server' | 'gitlab'
|
||||
|
||||
@ -232,7 +232,7 @@ export interface CodeHost extends ApplyLinkPreviewOptions {
|
||||
*/
|
||||
urlToFile?: (
|
||||
sourcegraphURL: string,
|
||||
target: RepoSpec & RawRepoSpec & RevSpec & FileSpec & Partial<UIPositionSpec> & Partial<ViewStateSpec>,
|
||||
target: RepoSpec & RawRepoSpec & RevisionSpec & FileSpec & Partial<UIPositionSpec> & Partial<ViewStateSpec>,
|
||||
context: URLToFileContext
|
||||
) => string
|
||||
|
||||
@ -276,9 +276,9 @@ export interface FileInfo {
|
||||
*/
|
||||
commitID: string
|
||||
/**
|
||||
* The revision the code view is at. If a `baseRev` is provided, this value is treated as the head rev.
|
||||
* The revision the code view is at. If a `baseRev` is provided, this value is treated as the head revision.
|
||||
*/
|
||||
rev?: string
|
||||
revision?: string
|
||||
/**
|
||||
* The repo name for the BASE side of a diff. This is useful for Phabricator
|
||||
* staging areas since they are separate repos.
|
||||
@ -295,7 +295,7 @@ export interface FileInfo {
|
||||
/**
|
||||
* Revision for the BASE side of the diff.
|
||||
*/
|
||||
baseRev?: string
|
||||
baseRevision?: string
|
||||
}
|
||||
|
||||
export interface FileInfoWithRepoNames extends FileInfo, RepoSpec {
|
||||
@ -349,7 +349,7 @@ function initCodeIntelligence({
|
||||
mutations: Observable<MutationRecordLike[]>
|
||||
}): {
|
||||
hoverifier: Hoverifier<
|
||||
RepoSpec & RevSpec & FileSpec & ResolvedRevSpec,
|
||||
RepoSpec & RevisionSpec & FileSpec & ResolvedRevisionSpec,
|
||||
HoverData<ExtensionHoverAlertType>,
|
||||
ActionItemAction
|
||||
>
|
||||
@ -378,7 +378,7 @@ function initCodeIntelligence({
|
||||
|
||||
// Code views come and go, but there is always a single hoverifier on the page
|
||||
const hoverifier = createHoverifier<
|
||||
RepoSpec & RevSpec & FileSpec & ResolvedRevSpec,
|
||||
RepoSpec & RevisionSpec & FileSpec & ResolvedRevisionSpec,
|
||||
HoverData<ExtensionHoverAlertType>,
|
||||
ActionItemAction
|
||||
>({
|
||||
@ -392,7 +392,7 @@ function initCodeIntelligence({
|
||||
getHover: ({ line, character, part, ...rest }) =>
|
||||
combineLatest([
|
||||
extensionsController.services.textDocumentHover.getHover(
|
||||
toTextDocumentPositionParams({ ...rest, position: { line, character } })
|
||||
toTextDocumentPositionParameters({ ...rest, position: { line, character } })
|
||||
),
|
||||
getActiveHoverAlerts(hoverAlerts),
|
||||
]).pipe(
|
||||
@ -666,8 +666,8 @@ export function handleCodeHost({
|
||||
const repoExistsOrErrors = signInCloses.pipe(
|
||||
startWith(null),
|
||||
switchMap(() => {
|
||||
const { rawRepoName, rev } = getContext()
|
||||
return resolveRev({ repoName: rawRepoName, rev, requestGraphQL }).pipe(
|
||||
const { rawRepoName, revision } = getContext()
|
||||
return resolveRevision({ repoName: rawRepoName, revision, requestGraphQL }).pipe(
|
||||
retryWhenCloneInProgressError(),
|
||||
mapTo(true),
|
||||
catchError(error => {
|
||||
@ -738,12 +738,12 @@ export function handleCodeHost({
|
||||
}))
|
||||
)
|
||||
),
|
||||
catchError(err => {
|
||||
catchError(error => {
|
||||
// Ignore PrivateRepoPublicSourcegraph errors (don't initialize those code views)
|
||||
if (isPrivateRepoPublicSourcegraphComErrorLike(err)) {
|
||||
if (isPrivateRepoPublicSourcegraphComErrorLike(error)) {
|
||||
return EMPTY
|
||||
}
|
||||
throw err
|
||||
throw error
|
||||
}),
|
||||
tap({
|
||||
error: error => {
|
||||
@ -791,7 +791,7 @@ export function handleCodeHost({
|
||||
)
|
||||
|
||||
/** Map from workspace URI to number of editors referencing it */
|
||||
const rootRefCounts = new Map<string, number>()
|
||||
const rootReferenceCounts = new Map<string, number>()
|
||||
|
||||
/**
|
||||
* Adds root referenced by a code editor to the worskpace.
|
||||
@ -799,9 +799,9 @@ export function handleCodeHost({
|
||||
* Will only cause `workspace.roots` to emit if no root with
|
||||
* the given `uri` existed.
|
||||
*/
|
||||
const addRootRef = (uri: string, inputRevision: string | undefined): void => {
|
||||
rootRefCounts.set(uri, (rootRefCounts.get(uri) || 0) + 1)
|
||||
if (rootRefCounts.get(uri) === 1) {
|
||||
const addRootReference = (uri: string, inputRevision: string | undefined): void => {
|
||||
rootReferenceCounts.set(uri, (rootReferenceCounts.get(uri) || 0) + 1)
|
||||
if (rootReferenceCounts.get(uri) === 1) {
|
||||
extensionsController.services.workspace.roots.next([
|
||||
...extensionsController.services.workspace.roots.value,
|
||||
{ uri, inputRevision },
|
||||
@ -815,18 +815,18 @@ export function handleCodeHost({
|
||||
* Will only cause `workspace.roots` to emit if the root
|
||||
* with the given `uri` has no more references.
|
||||
*/
|
||||
const deleteRootRef = (uri: string): void => {
|
||||
const currentRefCount = rootRefCounts.get(uri)
|
||||
if (!currentRefCount) {
|
||||
const deleteRootReference = (uri: string): void => {
|
||||
const currentReferenceCount = rootReferenceCounts.get(uri)
|
||||
if (!currentReferenceCount) {
|
||||
throw new Error(`No preexisting root refs for uri ${uri}`)
|
||||
}
|
||||
const updatedRefCount = currentRefCount - 1
|
||||
if (updatedRefCount === 0) {
|
||||
const updatedReferenceCount = currentReferenceCount - 1
|
||||
if (updatedReferenceCount === 0) {
|
||||
extensionsController.services.workspace.roots.next(
|
||||
extensionsController.services.workspace.roots.value.filter(root => root.uri !== uri)
|
||||
)
|
||||
} else {
|
||||
rootRefCounts.set(uri, updatedRefCount)
|
||||
rootReferenceCounts.set(uri, updatedReferenceCount)
|
||||
}
|
||||
}
|
||||
|
||||
@ -857,9 +857,9 @@ export function handleCodeHost({
|
||||
model,
|
||||
}
|
||||
const rootURI = toRootURI(fileInfo)
|
||||
addRootRef(rootURI, fileInfo.rev)
|
||||
addRootReference(rootURI, fileInfo.revision)
|
||||
codeViewEvent.subscriptions.add(() => {
|
||||
deleteRootRef(rootURI)
|
||||
deleteRootReference(rootURI)
|
||||
extensionsController.services.viewer.removeViewer(editorId)
|
||||
})
|
||||
|
||||
@ -900,9 +900,9 @@ export function handleCodeHost({
|
||||
repoName: fileInfo.baseRepoName,
|
||||
commitID: fileInfo.baseCommitID,
|
||||
})
|
||||
addRootRef(baseRootURI, fileInfo.baseRev)
|
||||
addRootReference(baseRootURI, fileInfo.baseRevision)
|
||||
codeViewEvent.subscriptions.add(() => {
|
||||
deleteRootRef(baseRootURI)
|
||||
deleteRootReference(baseRootURI)
|
||||
extensionsController.services.viewer.removeViewer(editor)
|
||||
})
|
||||
}
|
||||
@ -981,11 +981,16 @@ export function handleCodeHost({
|
||||
}
|
||||
|
||||
// Add hover code intelligence
|
||||
const resolveContext: ContextResolver<RepoSpec & RevSpec & FileSpec & ResolvedRevSpec> = ({ part }) => ({
|
||||
const resolveContext: ContextResolver<RepoSpec & RevisionSpec & FileSpec & ResolvedRevisionSpec> = ({
|
||||
part,
|
||||
}) => ({
|
||||
repoName: part === 'base' ? fileInfo.baseRepoName || fileInfo.repoName : fileInfo.repoName,
|
||||
commitID: part === 'base' ? fileInfo.baseCommitID! : fileInfo.commitID,
|
||||
filePath: part === 'base' ? fileInfo.baseFilePath || fileInfo.filePath : fileInfo.filePath,
|
||||
rev: part === 'base' ? fileInfo.baseRev || fileInfo.baseCommitID! : fileInfo.rev || fileInfo.commitID,
|
||||
revision:
|
||||
part === 'base'
|
||||
? fileInfo.baseRevision || fileInfo.baseCommitID!
|
||||
: fileInfo.revision || fileInfo.commitID,
|
||||
})
|
||||
const adjustPosition = getPositionAdjuster?.(platformContext.requestGraphQL)
|
||||
let hoverSubscription = new Subscription()
|
||||
|
||||
@ -225,15 +225,17 @@ export function testDOMFunctions(
|
||||
beforeEach(setCodeElement)
|
||||
it('should return correctly whether the first character is a diff indicator', () => {
|
||||
// Default is false
|
||||
const is = Boolean(
|
||||
const actualFirstCharacterIsDiffIndicator = Boolean(
|
||||
domFunctions.isFirstCharacterDiffIndicator &&
|
||||
domFunctions.isFirstCharacterDiffIndicator(codeElement)
|
||||
)
|
||||
expect(is).toBe(Boolean(firstCharacterIsDiffIndicator))
|
||||
if (is) {
|
||||
expect(actualFirstCharacterIsDiffIndicator).toBe(Boolean(firstCharacterIsDiffIndicator))
|
||||
if (actualFirstCharacterIsDiffIndicator) {
|
||||
// Check that the first character is truly a diff indicator
|
||||
const diffIndicators = new Set(['+', '-', ' '])
|
||||
expect(is).toBe(diffIndicators.has(codeElement.textContent![0]))
|
||||
expect(actualFirstCharacterIsDiffIndicator).toBe(
|
||||
diffIndicators.has(codeElement.textContent![0])
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@ import { catchError, map, switchMap } from 'rxjs/operators'
|
||||
import { Omit } from 'utility-types'
|
||||
import { isPrivateRepoPublicSourcegraphComErrorLike } from '../../../../../shared/src/backend/errors'
|
||||
import { PlatformContext } from '../../../../../shared/src/platform/context'
|
||||
import { FileSpec, RepoSpec, ResolvedRevSpec, RevSpec } from '../../../../../shared/src/util/url'
|
||||
import { FileSpec, RepoSpec, ResolvedRevisionSpec, RevisionSpec } from '../../../../../shared/src/util/url'
|
||||
import { ButtonProps } from '../../components/CodeViewToolbar'
|
||||
import { fetchBlobContentLines } from '../../repo/backend'
|
||||
import { CodeHost, FileInfo, FileInfoWithRepoNames } from './codeHost'
|
||||
@ -57,7 +57,7 @@ export interface CodeView {
|
||||
*/
|
||||
getPositionAdjuster?: (
|
||||
requestGraphQL: PlatformContext['requestGraphQL']
|
||||
) => PositionAdjuster<RepoSpec & RevSpec & FileSpec & ResolvedRevSpec>
|
||||
) => PositionAdjuster<RepoSpec & RevisionSpec & FileSpec & ResolvedRevisionSpec>
|
||||
/** Props for styling the buttons in the `CodeViewToolbar`. */
|
||||
toolbarButtonProps?: ButtonProps
|
||||
/**
|
||||
@ -134,10 +134,10 @@ export const fetchFileContents = (
|
||||
catchError(() => [info])
|
||||
)
|
||||
}),
|
||||
catchError(err => {
|
||||
if (isPrivateRepoPublicSourcegraphComErrorLike(err)) {
|
||||
catchError(error => {
|
||||
if (isPrivateRepoPublicSourcegraphComErrorLike(error)) {
|
||||
return [info]
|
||||
}
|
||||
throw err
|
||||
throw error
|
||||
})
|
||||
)
|
||||
|
||||
@ -21,10 +21,10 @@ describe('contentViews', () => {
|
||||
})
|
||||
|
||||
const createTestElement = (): HTMLElement => {
|
||||
const el = document.createElement('div')
|
||||
el.className = `test test-${uniqueId()}`
|
||||
document.body.append(el)
|
||||
return el
|
||||
const element = document.createElement('div')
|
||||
element.className = `test test-${uniqueId()}`
|
||||
document.body.append(element)
|
||||
return element
|
||||
}
|
||||
|
||||
test('detects addition, mutation, and removal of content views (and annotates them)', async () => {
|
||||
@ -75,8 +75,8 @@ describe('contentViews', () => {
|
||||
},
|
||||
{
|
||||
contentViewResolvers: [{ selector: 'div', resolveView: () => ({ element }) }],
|
||||
setElementTooltip: (e, text) =>
|
||||
text !== null ? (e.dataset.tooltip = text) : e.removeAttribute('data-tooltip'),
|
||||
setElementTooltip: (element, text) =>
|
||||
text !== null ? (element.dataset.tooltip = text) : element.removeAttribute('data-tooltip'),
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -124,8 +124,8 @@ describe('contentViews', () => {
|
||||
},
|
||||
{
|
||||
contentViewResolvers: [{ selector: 'div', resolveView: () => ({ element }) }],
|
||||
setElementTooltip: (e, text) =>
|
||||
text !== null ? (e.dataset.tooltip = text) : e.removeAttribute('data-tooltip'),
|
||||
setElementTooltip: (element, text) =>
|
||||
text !== null ? (element.dataset.tooltip = text) : element.removeAttribute('data-tooltip'),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@ -182,7 +182,7 @@ export const applyDecorations = (
|
||||
if (decoration.after) {
|
||||
const style = decorationAttachmentStyleForTheme(decoration.after, IS_LIGHT_THEME)
|
||||
|
||||
const linkTo = (url: string) => (e: HTMLElement): HTMLElement => {
|
||||
const linkTo = (url: string) => (element: HTMLElement): HTMLElement => {
|
||||
const link = document.createElement('a')
|
||||
link.setAttribute('href', url)
|
||||
|
||||
@ -194,7 +194,7 @@ export const applyDecorations = (
|
||||
link.setAttribute('rel', 'noreferrer noopener')
|
||||
|
||||
link.style.color = style.color || ''
|
||||
link.append(e)
|
||||
link.append(element)
|
||||
return link
|
||||
}
|
||||
|
||||
|
||||
@ -24,8 +24,8 @@ export function getActiveHoverAlerts(
|
||||
map(alerts => (dismissedAlerts ? alerts.filter(({ type }) => !dismissedAlerts[type]) : alerts))
|
||||
)
|
||||
),
|
||||
catchError(err => {
|
||||
console.error('Error getting hover alerts', err)
|
||||
catchError(error => {
|
||||
console.error('Error getting hover alerts', error)
|
||||
return [undefined]
|
||||
}),
|
||||
startWith([])
|
||||
|
||||
@ -64,7 +64,7 @@ export function nativeTooltipsEnabledFromSettings(settings: PlatformContext['set
|
||||
map(({ final }) => final),
|
||||
filter(isDefined),
|
||||
filter(isNot<ErrorLike | Settings, ErrorLike>(isErrorLike)),
|
||||
map(s => !!s['codeHost.useNativeTooltips']),
|
||||
map(settings => !!settings['codeHost.useNativeTooltips']),
|
||||
distinctUntilChanged((a, b) => isEqual(a, b)),
|
||||
publishReplay(1),
|
||||
refCount()
|
||||
|
||||
@ -33,10 +33,10 @@ describe('textFields', () => {
|
||||
})
|
||||
|
||||
const createTestElement = (): HTMLTextAreaElement => {
|
||||
const el = document.createElement('textarea')
|
||||
el.className = `test test-${uniqueId()}`
|
||||
document.body.append(el)
|
||||
return el
|
||||
const element = document.createElement('textarea')
|
||||
element.className = `test test-${uniqueId()}`
|
||||
document.body.append(element)
|
||||
return element
|
||||
}
|
||||
|
||||
test('detects addition and removal of text fields', async () => {
|
||||
|
||||
@ -3,7 +3,7 @@ import { catchError, map } from 'rxjs/operators'
|
||||
|
||||
import { isPrivateRepoPublicSourcegraphComErrorLike } from '../../../../../../shared/src/backend/errors'
|
||||
import { PlatformContext } from '../../../../../../shared/src/platform/context'
|
||||
import { resolveRepo, resolveRev, retryWhenCloneInProgressError } from '../../../repo/backend'
|
||||
import { resolveRepo, resolveRevision, retryWhenCloneInProgressError } from '../../../repo/backend'
|
||||
import { FileInfo, FileInfoWithRepoNames } from '../codeHost'
|
||||
|
||||
export const ensureRevisionsAreCloned = (
|
||||
@ -15,18 +15,18 @@ export const ensureRevisionsAreCloned = (
|
||||
// revision cloned.
|
||||
|
||||
// Head
|
||||
const resolvingHeadRev = resolveRev({ repoName, rev: commitID, requestGraphQL }).pipe(
|
||||
const resolvingHeadRevision = resolveRevision({ repoName, revision: commitID, requestGraphQL }).pipe(
|
||||
retryWhenCloneInProgressError()
|
||||
)
|
||||
|
||||
const requests = [resolvingHeadRev]
|
||||
const requests = [resolvingHeadRevision]
|
||||
|
||||
// If theres a base, resolve it as well.
|
||||
if (baseCommitID) {
|
||||
const resolvingBaseRev = resolveRev({ repoName, rev: baseCommitID, requestGraphQL }).pipe(
|
||||
const resolvingBaseRevision = resolveRevision({ repoName, revision: baseCommitID, requestGraphQL }).pipe(
|
||||
retryWhenCloneInProgressError()
|
||||
)
|
||||
requests.push(resolvingBaseRev)
|
||||
requests.push(resolvingBaseRevision)
|
||||
}
|
||||
|
||||
return zip(...requests).pipe(map(() => ({ repoName, commitID, baseCommitID, ...rest })))
|
||||
@ -52,11 +52,11 @@ export const resolveRepoNames = (
|
||||
// without having pointed his browser extension to a self-hosted Sourcegraph instance that
|
||||
// has access to that code. In that case, it's impossible to resolve the repo names,
|
||||
// so we keep the repo names inferred from the code host's DOM.
|
||||
catchError(err => {
|
||||
if (isPrivateRepoPublicSourcegraphComErrorLike(err)) {
|
||||
catchError(error => {
|
||||
if (isPrivateRepoPublicSourcegraphComErrorLike(error)) {
|
||||
return [{ rawRepoName, baseRawRepoName, repoName: rawRepoName, baseRepoName: baseRawRepoName, ...rest }]
|
||||
}
|
||||
throw err
|
||||
throw error
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -190,11 +190,11 @@ describe('trackViews()', () => {
|
||||
.pipe(
|
||||
trackViews([{ selector: '.view', resolveView: element => ({ element }) }]),
|
||||
bufferCount(3),
|
||||
switchMap(async ([v1, v2, v3]) => {
|
||||
switchMap(async ([view1, view2, view3]) => {
|
||||
const v2Removed = sinon.spy(() => undefined)
|
||||
v2.subscriptions.add(v2Removed)
|
||||
const v1Removed = new Promise(resolve => v1.subscriptions.add(resolve))
|
||||
const v3Removed = new Promise(resolve => v3.subscriptions.add(resolve))
|
||||
view2.subscriptions.add(v2Removed)
|
||||
const v1Removed = new Promise(resolve => view1.subscriptions.add(resolve))
|
||||
const v3Removed = new Promise(resolve => view3.subscriptions.add(resolve))
|
||||
await Promise.all([v1Removed, v3Removed])
|
||||
sinon.assert.notCalled(v2Removed)
|
||||
})
|
||||
@ -288,8 +288,8 @@ describe('delayUntilIntersecting()', () => {
|
||||
subscriptions.add(
|
||||
from(views)
|
||||
.pipe(
|
||||
delayUntilIntersecting({}, cb => {
|
||||
observerCallback = cb
|
||||
delayUntilIntersecting({}, callback => {
|
||||
observerCallback = callback
|
||||
return {
|
||||
observe,
|
||||
unobserve,
|
||||
|
||||
@ -120,20 +120,20 @@ export type IntersectionObserverLike = Pick<IntersectionObserver, 'observe' | 'u
|
||||
export function delayUntilIntersecting<T extends View>(
|
||||
options: IntersectionObserverInit,
|
||||
createIntersectionObserver = (
|
||||
cb: IntersectionObserverCallbackLike,
|
||||
callback: IntersectionObserverCallbackLike,
|
||||
options: IntersectionObserverInit
|
||||
): IntersectionObserverLike => new IntersectionObserver(cb, options)
|
||||
): IntersectionObserverLike => new IntersectionObserver(callback, options)
|
||||
): OperatorFunction<ViewWithSubscriptions<T>, ViewWithSubscriptions<T>> {
|
||||
return views =>
|
||||
new Observable(viewObserver => {
|
||||
const subscriptions = new Subscription()
|
||||
const delayedViews = new Map<HTMLElement, ViewWithSubscriptions<T>>()
|
||||
const intersectionObserver = createIntersectionObserver((entries, obs) => {
|
||||
const intersectionObserver = createIntersectionObserver((entries, observer) => {
|
||||
for (const entry of entries) {
|
||||
const target = entry.target as HTMLElement
|
||||
if (entry.isIntersecting && delayedViews.get(target)) {
|
||||
viewObserver.next(delayedViews.get(target))
|
||||
obs.unobserve(entry.target)
|
||||
observer.unobserve(entry.target)
|
||||
delayedViews.delete(target)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,15 +80,15 @@ export const CodeViewToolbar: React.FunctionComponent<CodeViewToolbarProps> = pr
|
||||
sourcegraphURL: props.sourcegraphURL,
|
||||
repoName: props.fileInfoOrError.baseRepoName || props.fileInfoOrError.repoName,
|
||||
filePath: props.fileInfoOrError.baseFilePath || props.fileInfoOrError.filePath,
|
||||
rev: props.fileInfoOrError.baseRev || props.fileInfoOrError.baseCommitID,
|
||||
revision: props.fileInfoOrError.baseRevision || props.fileInfoOrError.baseCommitID,
|
||||
query: {
|
||||
diff: {
|
||||
rev: props.fileInfoOrError.baseCommitID,
|
||||
revision: props.fileInfoOrError.baseCommitID,
|
||||
},
|
||||
},
|
||||
commit: {
|
||||
baseRev: props.fileInfoOrError.baseRev || props.fileInfoOrError.baseCommitID,
|
||||
headRev: props.fileInfoOrError.rev || props.fileInfoOrError.commitID,
|
||||
baseRev: props.fileInfoOrError.baseRevision || props.fileInfoOrError.baseCommitID,
|
||||
headRev: props.fileInfoOrError.revision || props.fileInfoOrError.commitID,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@ -109,7 +109,7 @@ export const CodeViewToolbar: React.FunctionComponent<CodeViewToolbarProps> = pr
|
||||
sourcegraphURL: props.sourcegraphURL,
|
||||
repoName: props.fileInfoOrError.repoName,
|
||||
filePath: props.fileInfoOrError.filePath,
|
||||
rev: props.fileInfoOrError.rev || props.fileInfoOrError.commitID,
|
||||
revision: props.fileInfoOrError.revision || props.fileInfoOrError.commitID,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
|
||||
@ -47,8 +47,8 @@ export class OpenDiffOnSourcegraph extends React.Component<Props, State> {
|
||||
// Only include the relevant file diff.
|
||||
nodes: fileDiff.nodes.filter(node => node.oldPath === this.props.openProps.filePath),
|
||||
})),
|
||||
catchError(err => {
|
||||
console.error(err)
|
||||
catchError(error => {
|
||||
console.error(error)
|
||||
return [undefined]
|
||||
})
|
||||
)
|
||||
|
||||
@ -27,15 +27,15 @@ export class OpenOnSourcegraph extends React.Component<Props, {}> {
|
||||
if (props.commit) {
|
||||
return `${url}/-/compare/${props.commit.baseRev}...${props.commit.headRev}?utm_source=${getPlatformName()}`
|
||||
}
|
||||
if (props.rev) {
|
||||
url = `${url}@${props.rev}`
|
||||
if (props.revision) {
|
||||
url = `${url}@${props.revision}`
|
||||
}
|
||||
if (props.filePath) {
|
||||
url = `${url}/-/blob/${props.filePath}`
|
||||
}
|
||||
if (props.query) {
|
||||
if (props.query.diff) {
|
||||
url = `${url}?diff=${props.query.diff.rev}&utm_source=${getPlatformName()}`
|
||||
url = `${url}?diff=${props.query.diff.revision}&utm_source=${getPlatformName()}`
|
||||
} else if (props.query.search) {
|
||||
url = `${url}?q=${props.query.search}&utm_source=${getPlatformName()}`
|
||||
}
|
||||
|
||||
@ -1,43 +1,43 @@
|
||||
enum AppEnv {
|
||||
enum AppEnvironment {
|
||||
Extension,
|
||||
Page,
|
||||
}
|
||||
|
||||
enum ScriptEnv {
|
||||
enum ScriptEnvironment {
|
||||
Content,
|
||||
Background,
|
||||
Options,
|
||||
}
|
||||
|
||||
interface AppContext {
|
||||
appEnv: AppEnv
|
||||
scriptEnv: ScriptEnv
|
||||
appEnvironment: AppEnvironment
|
||||
scriptEnvironment: ScriptEnvironment
|
||||
}
|
||||
|
||||
function getContext(): AppContext {
|
||||
const appEnv = window.SG_ENV === 'EXTENSION' ? AppEnv.Extension : AppEnv.Page
|
||||
const appEnvironment = window.SG_ENV === 'EXTENSION' ? AppEnvironment.Extension : AppEnvironment.Page
|
||||
|
||||
let scriptEnv: ScriptEnv = ScriptEnv.Content
|
||||
if (appEnv === AppEnv.Extension) {
|
||||
let scriptEnvironment: ScriptEnvironment = ScriptEnvironment.Content
|
||||
if (appEnvironment === AppEnvironment.Extension) {
|
||||
if (window.location.pathname.includes('options.html')) {
|
||||
scriptEnv = ScriptEnv.Options
|
||||
scriptEnvironment = ScriptEnvironment.Options
|
||||
} else if (globalThis.browser && browser.runtime.getBackgroundPage) {
|
||||
scriptEnv = ScriptEnv.Background
|
||||
scriptEnvironment = ScriptEnvironment.Background
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
appEnv,
|
||||
scriptEnv,
|
||||
appEnvironment,
|
||||
scriptEnvironment,
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = getContext()
|
||||
const context = getContext()
|
||||
|
||||
export const isBackground = ctx.scriptEnv === ScriptEnv.Background
|
||||
export const isOptions = ctx.scriptEnv === ScriptEnv.Options
|
||||
export const isBackground = context.scriptEnvironment === ScriptEnvironment.Background
|
||||
export const isOptions = context.scriptEnvironment === ScriptEnvironment.Options
|
||||
|
||||
export const isExtension = ctx.appEnv === AppEnv.Extension
|
||||
export const isExtension = context.appEnvironment === AppEnvironment.Extension
|
||||
export const isInPage = !isExtension
|
||||
|
||||
export const isPhabricator = Boolean(document.querySelector('.phabricator-wordmark'))
|
||||
|
||||
@ -85,7 +85,7 @@ export function browserPortToMessagePort(
|
||||
const adapterListener = (event: MessageEvent): void => {
|
||||
const data: Message = event.data
|
||||
// Message from comlink needing to be forwarded to browser port with MessagePorts removed
|
||||
const portRefs: PortRef[] = []
|
||||
const portReferences: PortReference[] = []
|
||||
// Find message port references and connect to the other side with the found IDs.
|
||||
for (const { value, path, key, parent } of iteratePropertiesDeep(data)) {
|
||||
if (value instanceof MessagePort) {
|
||||
@ -96,11 +96,11 @@ export function browserPortToMessagePort(
|
||||
const browserPort = connect(prefix + id)
|
||||
link(browserPort, value)
|
||||
// Include the ID of the browser port in the message
|
||||
portRefs.push({ path, id })
|
||||
portReferences.push({ path, id })
|
||||
}
|
||||
}
|
||||
// Wrap message for the browser port to include all port IDs
|
||||
const browserPortMessage: BrowserPortMessage = { message: data, portRefs }
|
||||
const browserPortMessage: BrowserPortMessage = { message: data, portRefs: portReferences }
|
||||
|
||||
browserPort.postMessage(browserPortMessage)
|
||||
|
||||
@ -115,15 +115,15 @@ export function browserPortToMessagePort(
|
||||
|
||||
const browserPortListener = ({ message, portRefs }: BrowserPortMessage): void => {
|
||||
const transfer: MessagePort[] = []
|
||||
for (const portRef of portRefs) {
|
||||
for (const portReference of portRefs) {
|
||||
const { port1: comlinkMessagePort, port2: intermediateMessagePort } = new MessageChannel()
|
||||
|
||||
// Replace the port reference at the given path with a MessagePort that will be transferred.
|
||||
replaceValueAtPath(message, portRef.path, comlinkMessagePort)
|
||||
replaceValueAtPath(message, portReference.path, comlinkMessagePort)
|
||||
transfer.push(comlinkMessagePort)
|
||||
|
||||
// Once the port with the mentioned ID is connected, link it up
|
||||
whenConnected(portRef.id, browserPort => link(browserPort, intermediateMessagePort))
|
||||
whenConnected(portReference.id, browserPort => link(browserPort, intermediateMessagePort))
|
||||
}
|
||||
|
||||
// Forward message, with MessagePorts
|
||||
@ -154,7 +154,7 @@ export function browserPortToMessagePort(
|
||||
}
|
||||
}
|
||||
|
||||
interface PortRef {
|
||||
interface PortReference {
|
||||
/** Path at which the MessagePort appeared. */
|
||||
path: Path
|
||||
|
||||
@ -174,7 +174,7 @@ interface BrowserPortMessage {
|
||||
/**
|
||||
* Where in the message `MessagePort`s were referenced and the ID of the `browser.runtime.Port` created for each.
|
||||
*/
|
||||
portRefs: PortRef[]
|
||||
portRefs: PortReference[]
|
||||
}
|
||||
|
||||
type Key = string | number | symbol
|
||||
@ -209,12 +209,12 @@ interface PropertyIteratorEntry<T = unknown> {
|
||||
* @returns The old value at the path.
|
||||
*/
|
||||
function replaceValueAtPath(value: any, path: Path, newValue: unknown): unknown {
|
||||
const lastProp = path[path.length - 1]
|
||||
for (const prop of path.slice(0, -1)) {
|
||||
value = value[prop]
|
||||
const lastProperty = path[path.length - 1]
|
||||
for (const property of path.slice(0, -1)) {
|
||||
value = value[property]
|
||||
}
|
||||
const oldValue = value[lastProp]
|
||||
value[lastProp] = newValue
|
||||
const oldValue = value[lastProperty]
|
||||
value[lastProperty] = newValue
|
||||
return oldValue
|
||||
}
|
||||
|
||||
|
||||
@ -153,30 +153,30 @@ export function fetchViewerSettings(
|
||||
* Applies an edit and persists the result to client settings.
|
||||
*/
|
||||
export async function editClientSettings(edit: SettingsEdit | string): Promise<void> {
|
||||
const getNext = (prev: string): string =>
|
||||
const getNext = (previous: string): string =>
|
||||
typeof edit === 'string'
|
||||
? edit
|
||||
: applyEdits(
|
||||
prev,
|
||||
previous,
|
||||
// TODO(chris): remove `.slice()` (which guards against mutation) once
|
||||
// https://github.com/Microsoft/node-jsonc-parser/pull/12 is merged in.
|
||||
setProperty(prev, edit.path.slice(), edit.value, {
|
||||
setProperty(previous, edit.path.slice(), edit.value, {
|
||||
tabSize: 2,
|
||||
insertSpaces: true,
|
||||
eol: '\n',
|
||||
})
|
||||
)
|
||||
if (isInPage) {
|
||||
const prev = localStorage.getItem(inPageClientSettingsKey) || ''
|
||||
const next = getNext(prev)
|
||||
const previous = localStorage.getItem(inPageClientSettingsKey) || ''
|
||||
const next = getNext(previous)
|
||||
|
||||
localStorage.setItem(inPageClientSettingsKey, next)
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const { clientSettings: prev = '{}' } = await storage.sync.get()
|
||||
const next = getNext(prev)
|
||||
const { clientSettings: previous = '{}' } = await storage.sync.get()
|
||||
const next = getNext(previous)
|
||||
|
||||
await storage.sync.set({ clientSettings: next })
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { delay, filter, map, retryWhen } from 'rxjs/operators'
|
||||
import {
|
||||
CloneInProgressError,
|
||||
RepoNotFoundError,
|
||||
RevNotFoundError,
|
||||
RevisionNotFoundError,
|
||||
isCloneInProgressErrorLike,
|
||||
} from '../../../../shared/src/backend/errors'
|
||||
import { dataOrThrowErrors, gql } from '../../../../shared/src/graphql/graphql'
|
||||
@ -11,7 +11,14 @@ import * as GQL from '../../../../shared/src/graphql/schema'
|
||||
import { PlatformContext } from '../../../../shared/src/platform/context'
|
||||
import { createAggregateError } from '../../../../shared/src/util/errors'
|
||||
import { memoizeObservable } from '../../../../shared/src/util/memoizeObservable'
|
||||
import { FileSpec, makeRepoURI, RawRepoSpec, RepoSpec, ResolvedRevSpec, RevSpec } from '../../../../shared/src/util/url'
|
||||
import {
|
||||
FileSpec,
|
||||
makeRepoURI,
|
||||
RawRepoSpec,
|
||||
RepoSpec,
|
||||
ResolvedRevisionSpec,
|
||||
RevisionSpec,
|
||||
} from '../../../../shared/src/util/url'
|
||||
|
||||
/**
|
||||
* @returns Observable that emits if the repo exists on the instance.
|
||||
@ -46,39 +53,39 @@ export const resolveRepo = memoizeObservable(
|
||||
/**
|
||||
* @returns Observable that emits the commit ID. Errors with a `CloneInProgressError` if the repo is still being cloned.
|
||||
*/
|
||||
export const resolveRev = memoizeObservable(
|
||||
export const resolveRevision = memoizeObservable(
|
||||
({
|
||||
requestGraphQL,
|
||||
...ctx
|
||||
}: RepoSpec & Partial<RevSpec> & Pick<PlatformContext, 'requestGraphQL'>): Observable<string> =>
|
||||
...context
|
||||
}: RepoSpec & Partial<RevisionSpec> & Pick<PlatformContext, 'requestGraphQL'>): Observable<string> =>
|
||||
from(
|
||||
requestGraphQL<GQL.IQuery>({
|
||||
request: gql`
|
||||
query ResolveRev($repoName: String!, $rev: String!) {
|
||||
query ResolveRev($repoName: String!, $revision: String!) {
|
||||
repository(name: $repoName) {
|
||||
mirrorInfo {
|
||||
cloned
|
||||
}
|
||||
commit(rev: $rev) {
|
||||
commit(rev: $revision) {
|
||||
oid
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { ...ctx, rev: ctx.rev || '' },
|
||||
variables: { ...context, revision: context.revision || '' },
|
||||
mightContainPrivateInfo: true,
|
||||
})
|
||||
).pipe(
|
||||
map(dataOrThrowErrors),
|
||||
map(({ repository }) => {
|
||||
if (!repository) {
|
||||
throw new RepoNotFoundError(ctx.repoName)
|
||||
throw new RepoNotFoundError(context.repoName)
|
||||
}
|
||||
if (!repository.mirrorInfo.cloned) {
|
||||
throw new CloneInProgressError(ctx.repoName)
|
||||
throw new CloneInProgressError(context.repoName)
|
||||
}
|
||||
if (!repository.commit) {
|
||||
throw new RevNotFoundError(ctx.rev)
|
||||
throw new RevisionNotFoundError(context.revision)
|
||||
}
|
||||
return repository.commit.oid
|
||||
})
|
||||
@ -91,13 +98,13 @@ export function retryWhenCloneInProgressError<T>(): (v: Observable<T>) => Observ
|
||||
maybeErrors.pipe(
|
||||
retryWhen(errors =>
|
||||
errors.pipe(
|
||||
filter(err => {
|
||||
if (isCloneInProgressErrorLike(err)) {
|
||||
filter(error => {
|
||||
if (isCloneInProgressErrorLike(error)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Don't swallow other errors.
|
||||
throw err
|
||||
throw error
|
||||
}),
|
||||
delay(1000)
|
||||
)
|
||||
@ -114,8 +121,8 @@ export function retryWhenCloneInProgressError<T>(): (v: Observable<T>) => Observ
|
||||
export const fetchBlobContentLines = memoizeObservable(
|
||||
({
|
||||
requestGraphQL,
|
||||
...ctx
|
||||
}: RepoSpec & ResolvedRevSpec & FileSpec & Pick<PlatformContext, 'requestGraphQL'>): Observable<string[]> =>
|
||||
...context
|
||||
}: RepoSpec & ResolvedRevisionSpec & FileSpec & Pick<PlatformContext, 'requestGraphQL'>): Observable<string[]> =>
|
||||
from(
|
||||
requestGraphQL<GQL.IQuery>({
|
||||
request: gql`
|
||||
@ -129,7 +136,7 @@ export const fetchBlobContentLines = memoizeObservable(
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: ctx,
|
||||
variables: context,
|
||||
mightContainPrivateInfo: true,
|
||||
})
|
||||
).pipe(
|
||||
@ -139,9 +146,9 @@ export const fetchBlobContentLines = memoizeObservable(
|
||||
}
|
||||
if (errors) {
|
||||
if (errors.length === 1) {
|
||||
const err = errors[0]
|
||||
const isFileContent = err.path.join('.') === 'repository.commit.file.content'
|
||||
const isDNE = err.message.includes('does not exist')
|
||||
const error = errors[0]
|
||||
const isFileContent = error.path.join('.') === 'repository.commit.file.content'
|
||||
const isDNE = error.message.includes('does not exist')
|
||||
|
||||
// The error is the file DNE. Just ignore it and pass an empty array
|
||||
// to represent this.
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { RepoSpec, RevisionSpec } from '../../../../shared/src/util/url'
|
||||
|
||||
export interface DiffResolvedRevSpec {
|
||||
baseCommitID: string
|
||||
headCommitID: string
|
||||
}
|
||||
|
||||
export interface OpenInSourcegraphProps {
|
||||
export interface OpenInSourcegraphProps extends RepoSpec, RevisionSpec {
|
||||
sourcegraphURL: string
|
||||
repoName: string
|
||||
rev: string
|
||||
filePath?: string
|
||||
commit?: {
|
||||
baseRev: string
|
||||
@ -20,7 +20,7 @@ export interface OpenInSourcegraphProps {
|
||||
query?: {
|
||||
search?: string
|
||||
diff?: {
|
||||
rev: string
|
||||
revision: string
|
||||
}
|
||||
}
|
||||
withModifierKey?: boolean
|
||||
|
||||
@ -37,9 +37,9 @@ const createFeatureFlagStorage = ({ get, set }: FeatureFlagUtilities): FeatureFl
|
||||
return typeof value === 'boolean' ? value : featureFlagDefaults[key]
|
||||
},
|
||||
async toggle<K extends keyof FeatureFlags>(key: K): Promise<boolean> {
|
||||
const val = await get(key)
|
||||
await set(key, !val)
|
||||
return !val
|
||||
const value = await get(key)
|
||||
await set(key, !value)
|
||||
return !value
|
||||
},
|
||||
})
|
||||
|
||||
@ -48,9 +48,9 @@ async function bextGet<K extends keyof FeatureFlags>(key: K): Promise<boolean |
|
||||
return featureFlags[key]
|
||||
}
|
||||
|
||||
async function bextSet<K extends keyof FeatureFlags>(key: K, val: FeatureFlags[K]): Promise<void> {
|
||||
async function bextSet<K extends keyof FeatureFlags>(key: K, value: FeatureFlags[K]): Promise<void> {
|
||||
const { featureFlags } = await storage.sync.get('featureFlags')
|
||||
await storage.sync.set({ featureFlags: { ...featureFlags, [key]: val } })
|
||||
await storage.sync.set({ featureFlags: { ...featureFlags, [key]: value } })
|
||||
}
|
||||
|
||||
const browserExtensionFeatureFlags = createFeatureFlagStorage({
|
||||
@ -65,8 +65,8 @@ const inPageFeatureFlags = createFeatureFlagStorage({
|
||||
return value === null ? undefined : value === 'true'
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
set: async (key, val) => {
|
||||
localStorage.setItem(key, String(val))
|
||||
set: async (key, value) => {
|
||||
localStorage.setItem(key, String(value))
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -27,6 +27,6 @@
|
||||
"./build/**/*",
|
||||
"coverage",
|
||||
"stories", // TODO fix type errors and include
|
||||
"src/e2e",
|
||||
"src/end-to-end",
|
||||
],
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import * as path from 'path'
|
||||
import execa from 'execa'
|
||||
const mkdtemp = promisify(original_mkdtemp)
|
||||
|
||||
const formatDate = (d: Date): string => `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}`
|
||||
const formatDate = (date: Date): string => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
|
||||
|
||||
export async function ensureTrackingIssue({
|
||||
majorVersion,
|
||||
@ -48,7 +48,7 @@ export async function ensureTrackingIssue({
|
||||
per_page: 100,
|
||||
direction: 'desc',
|
||||
})
|
||||
const milestone = milestones.data.filter(m => m.title === milestoneTitle)
|
||||
const milestone = milestones.data.filter(milestone => milestone.title === milestoneTitle)
|
||||
if (milestone.length === 0) {
|
||||
console.log(
|
||||
`Milestone ${JSON.stringify(
|
||||
@ -95,15 +95,15 @@ export async function ensurePatchReleaseIssue({
|
||||
|
||||
async function getContent(
|
||||
octokit: Octokit,
|
||||
params: {
|
||||
parameters: {
|
||||
owner: string
|
||||
repo: string
|
||||
path: string
|
||||
}
|
||||
): Promise<string> {
|
||||
const resp = await octokit.repos.getContents(params)
|
||||
const resp = await octokit.repos.getContents(parameters)
|
||||
if (Array.isArray(resp.data)) {
|
||||
throw new TypeError(`${params.path} is a directory`)
|
||||
throw new TypeError(`${parameters.path} is a directory`)
|
||||
}
|
||||
return Buffer.from(resp.data.content as string, 'base64').toString()
|
||||
}
|
||||
@ -189,7 +189,7 @@ export interface CreateBranchWithChangesOptions {
|
||||
export async function createBranchWithChanges({
|
||||
owner,
|
||||
repo,
|
||||
base: baseRev,
|
||||
base: baseRevision,
|
||||
head: headBranch,
|
||||
commitMessage,
|
||||
bashEditCommands,
|
||||
@ -202,7 +202,7 @@ export async function createBranchWithChanges({
|
||||
cd ${tmpdir};
|
||||
git clone --depth 10 git@github.com:${owner}/${repo} || git clone --depth 10 https://github.com/${owner}/${repo};
|
||||
cd ./${repo};
|
||||
git checkout ${baseRev};
|
||||
git checkout ${baseRevision};
|
||||
${bashEditCommands.join(';\n ')};
|
||||
git add :/;
|
||||
git commit -a -m ${JSON.stringify(commitMessage)};
|
||||
|
||||
@ -90,12 +90,12 @@ export async function ensureEvent(
|
||||
|
||||
async function listEvents(auth: OAuth2Client): Promise<calendar_v3.Schema$Event[] | undefined> {
|
||||
const calendar = google.calendar({ version: 'v3', auth })
|
||||
const res = await calendar.events.list({
|
||||
const result = await calendar.events.list({
|
||||
calendarId: 'primary',
|
||||
timeMin: new Date().toISOString(),
|
||||
maxResults: 2500,
|
||||
singleEvents: true,
|
||||
orderBy: 'startTime',
|
||||
})
|
||||
return res.data.items
|
||||
return result.data.items
|
||||
}
|
||||
|
||||
@ -20,12 +20,12 @@ import execa from 'execa'
|
||||
|
||||
const sed = process.platform === 'linux' ? 'sed' : 'gsed'
|
||||
|
||||
const formatDate = (d: Date): string =>
|
||||
`${d.toLocaleString('en-US', {
|
||||
const formatDate = (date: Date): string =>
|
||||
`${date.toLocaleString('en-US', {
|
||||
timeZone: 'America/Los_Angeles',
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
} as Intl.DateTimeFormatOptions)} (SF time) / ${d.toLocaleString('en-US', {
|
||||
} as Intl.DateTimeFormatOptions)} (SF time) / ${date.toLocaleString('en-US', {
|
||||
timeZone: 'Europe/Berlin',
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
@ -78,7 +78,9 @@ const steps: Step[] = [
|
||||
({ id, argNames }) =>
|
||||
'\t' +
|
||||
id +
|
||||
(argNames && argNames.length > 0 ? ' ' + argNames.map(n => `<${n}>`).join(' ') : '')
|
||||
(argNames && argNames.length > 0
|
||||
? ' ' + argNames.map(argumentName => `<${argumentName}>`).join(' ')
|
||||
: '')
|
||||
)
|
||||
.join('\n')
|
||||
)
|
||||
@ -86,13 +88,13 @@ const steps: Step[] = [
|
||||
},
|
||||
{
|
||||
id: '_test:google-calendar',
|
||||
run: async c => {
|
||||
run: async config => {
|
||||
const googleCalendar = await getClient()
|
||||
await ensureEvent(
|
||||
{
|
||||
title: 'TEST EVENT',
|
||||
startDateTime: new Date(c.releaseDateTime).toISOString(),
|
||||
endDateTime: addMinutes(new Date(c.releaseDateTime), 1).toISOString(),
|
||||
startDateTime: new Date(config.releaseDateTime).toISOString(),
|
||||
endDateTime: addMinutes(new Date(config.releaseDateTime), 1).toISOString(),
|
||||
},
|
||||
googleCalendar
|
||||
)
|
||||
@ -106,53 +108,53 @@ const steps: Step[] = [
|
||||
},
|
||||
{
|
||||
id: 'add-timeline-to-calendar',
|
||||
run: async c => {
|
||||
run: async config => {
|
||||
const googleCalendar = await getClient()
|
||||
const events: EventOptions[] = [
|
||||
{
|
||||
title: 'Release captain: prepare for branch cut (5 working days until release)',
|
||||
description: 'See the release tracking issue for TODOs',
|
||||
startDateTime: new Date(c.fiveWorkingDaysBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(c.fiveWorkingDaysBeforeRelease), 1).toISOString(),
|
||||
startDateTime: new Date(config.fiveWorkingDaysBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(config.fiveWorkingDaysBeforeRelease), 1).toISOString(),
|
||||
},
|
||||
{
|
||||
title: 'Release captain: branch cut (4 working days until release)',
|
||||
description: 'See the release tracking issue for TODOs',
|
||||
startDateTime: new Date(c.fourWorkingDaysBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(c.fourWorkingDaysBeforeRelease), 1).toISOString(),
|
||||
startDateTime: new Date(config.fourWorkingDaysBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(config.fourWorkingDaysBeforeRelease), 1).toISOString(),
|
||||
},
|
||||
...eachDayOfInterval({
|
||||
start: addDays(new Date(c.fourWorkingDaysBeforeRelease), 1),
|
||||
end: subDays(new Date(c.oneWorkingDayBeforeRelease), 1),
|
||||
start: addDays(new Date(config.fourWorkingDaysBeforeRelease), 1),
|
||||
end: subDays(new Date(config.oneWorkingDayBeforeRelease), 1),
|
||||
})
|
||||
.filter(d => !isWeekend(d))
|
||||
.map(d => ({
|
||||
.filter(date => !isWeekend(date))
|
||||
.map(date => ({
|
||||
title: 'Release captain: cut new release candidate',
|
||||
description: 'See release tracking issue for TODOs',
|
||||
startDateTime: d.toISOString(),
|
||||
endDateTime: addMinutes(d, 1).toISOString(),
|
||||
startDateTime: date.toISOString(),
|
||||
endDateTime: addMinutes(date, 1).toISOString(),
|
||||
})),
|
||||
{
|
||||
title: 'Release captain: tag final release (1 working day before release)',
|
||||
description: 'See the release tracking issue for TODOs',
|
||||
startDateTime: new Date(c.oneWorkingDayBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(c.oneWorkingDayBeforeRelease), 1).toISOString(),
|
||||
startDateTime: new Date(config.oneWorkingDayBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(config.oneWorkingDayBeforeRelease), 1).toISOString(),
|
||||
},
|
||||
{
|
||||
title: `Cut release branch ${c.majorVersion}.${c.minorVersion}`,
|
||||
title: `Cut release branch ${config.majorVersion}.${config.minorVersion}`,
|
||||
description: '(This is not an actual event to attend, just a calendar marker.)',
|
||||
anyoneCanAddSelf: true,
|
||||
attendees: [c.teamEmail],
|
||||
startDateTime: new Date(c.fourWorkingDaysBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(c.fourWorkingDaysBeforeRelease), 1).toISOString(),
|
||||
attendees: [config.teamEmail],
|
||||
startDateTime: new Date(config.fourWorkingDaysBeforeRelease).toISOString(),
|
||||
endDateTime: addMinutes(new Date(config.fourWorkingDaysBeforeRelease), 1).toISOString(),
|
||||
},
|
||||
{
|
||||
title: `Release Sourcegraph ${c.majorVersion}.${c.minorVersion}`,
|
||||
title: `Release Sourcegraph ${config.majorVersion}.${config.minorVersion}`,
|
||||
description: '(This is not an actual event to attend, just a calendar marker.)',
|
||||
anyoneCanAddSelf: true,
|
||||
attendees: [c.teamEmail],
|
||||
startDateTime: new Date(c.releaseDateTime).toISOString(),
|
||||
endDateTime: addMinutes(new Date(c.releaseDateTime), 1).toISOString(),
|
||||
attendees: [config.teamEmail],
|
||||
startDateTime: new Date(config.releaseDateTime).toISOString(),
|
||||
endDateTime: addMinutes(new Date(config.releaseDateTime), 1).toISOString(),
|
||||
},
|
||||
]
|
||||
|
||||
@ -187,25 +189,25 @@ const steps: Step[] = [
|
||||
},
|
||||
{
|
||||
id: 'tracking-issue:announce',
|
||||
run: async c => {
|
||||
run: async config => {
|
||||
const trackingIssueURL = await getIssueByTitle(
|
||||
await getAuthenticatedGitHubClient(),
|
||||
trackingIssueTitle(c.majorVersion, c.minorVersion)
|
||||
trackingIssueTitle(config.majorVersion, config.minorVersion)
|
||||
)
|
||||
if (!trackingIssueURL) {
|
||||
throw new Error(
|
||||
`Tracking issue for version ${c.majorVersion}.${c.minorVersion} not found--has it been create yet?`
|
||||
`Tracking issue for version ${config.majorVersion}.${config.minorVersion} not found--has it been create yet?`
|
||||
)
|
||||
}
|
||||
await postMessage(
|
||||
`:captain: ${c.majorVersion}.${c.minorVersion} Release :captain:
|
||||
Release captain: @${c.captainSlackUsername}
|
||||
`:captain: ${config.majorVersion}.${config.minorVersion} Release :captain:
|
||||
Release captain: @${config.captainSlackUsername}
|
||||
Tracking issue: ${trackingIssueURL}
|
||||
Key dates:
|
||||
- Release branch cut, testing commences: ${formatDate(new Date(c.fourWorkingDaysBeforeRelease))}
|
||||
- Final release tag: ${formatDate(new Date(c.oneWorkingDayBeforeRelease))}
|
||||
- Release: ${formatDate(new Date(c.releaseDateTime))}`,
|
||||
c.slackAnnounceChannel
|
||||
- Release branch cut, testing commences: ${formatDate(new Date(config.fourWorkingDaysBeforeRelease))}
|
||||
- Final release tag: ${formatDate(new Date(config.oneWorkingDayBeforeRelease))}
|
||||
- Release: ${formatDate(new Date(config.releaseDateTime))}`,
|
||||
config.slackAnnounceChannel
|
||||
)
|
||||
},
|
||||
},
|
||||
@ -232,13 +234,13 @@ Key dates:
|
||||
},
|
||||
{
|
||||
id: 'release-candidate:dev-announce',
|
||||
run: async (c, version) => {
|
||||
run: async (config, version) => {
|
||||
const parsedVersion = semver.parse(version, { loose: false })
|
||||
if (!parsedVersion) {
|
||||
throw new Error(`version ${version} is not valid semver`)
|
||||
}
|
||||
|
||||
const query = `is:open is:issue milestone:${c.majorVersion}.${c.minorVersion} label:release-blocker`
|
||||
const query = `is:open is:issue milestone:${config.majorVersion}.${config.minorVersion} label:release-blocker`
|
||||
const issues = await listIssues(await getAuthenticatedGitHubClient(), query)
|
||||
const issuesURL = `https://github.com/issues?q=${encodeURIComponent(query)}`
|
||||
const releaseBlockerMessage =
|
||||
@ -255,7 +257,7 @@ Key dates:
|
||||
- It will be deployed to k8s.sgdev.org within approximately one hour (https://k8s.sgdev.org/site-admin/updates)
|
||||
- ${releaseBlockerMessage}
|
||||
`
|
||||
await postMessage(message, c.slackAnnounceChannel)
|
||||
await postMessage(message, config.slackAnnounceChannel)
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -299,11 +301,11 @@ Key dates:
|
||||
throw new Error(`version ${version} is pre-release`)
|
||||
}
|
||||
const requiredCommands = ['comby', sed, 'find']
|
||||
for (const cmd of requiredCommands) {
|
||||
for (const command of requiredCommands) {
|
||||
try {
|
||||
await commandExists(cmd)
|
||||
await commandExists(command)
|
||||
} catch {
|
||||
throw new Error(`Required command ${cmd} does not exist`)
|
||||
throw new Error(`Required command ${command} does not exist`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,13 +363,13 @@ Key dates:
|
||||
},
|
||||
]
|
||||
|
||||
async function run(config: Config, stepIDToRun: StepID, ...stepArgs: string[]): Promise<void> {
|
||||
async function run(config: Config, stepIDToRun: StepID, ...stepArguments: string[]): Promise<void> {
|
||||
await Promise.all(
|
||||
steps
|
||||
.filter(({ id }) => id === stepIDToRun)
|
||||
.map(async step => {
|
||||
if (step.run) {
|
||||
await step.run(config, ...stepArgs)
|
||||
await step.run(config, ...stepArguments)
|
||||
}
|
||||
})
|
||||
)
|
||||
@ -389,8 +391,8 @@ async function main(): Promise<void> {
|
||||
console.error('Unrecognized step', JSON.stringify(step))
|
||||
return
|
||||
}
|
||||
const stepArgs = args.slice(1)
|
||||
await run(config, step as StepID, ...stepArgs)
|
||||
const stepArguments = args.slice(1)
|
||||
await run(config, step as StepID, ...stepArguments)
|
||||
}
|
||||
|
||||
main().catch(error => console.error(error))
|
||||
|
||||
@ -19,11 +19,11 @@ export async function readLine(prompt: string, cacheFile?: string): Promise<stri
|
||||
}
|
||||
|
||||
async function readLineNoCache(prompt: string): Promise<string> {
|
||||
const rl = readline.createInterface({
|
||||
const readlineInterface = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
})
|
||||
const userInput = await new Promise<string>(resolve => rl.question(prompt, resolve))
|
||||
rl.close()
|
||||
const userInput = await new Promise<string>(resolve => readlineInterface.question(prompt, resolve))
|
||||
readlineInterface.close()
|
||||
return userInput
|
||||
}
|
||||
|
||||
@ -20,8 +20,8 @@
|
||||
"graphql": "gulp graphQLTypes",
|
||||
"graphql-lint": "graphql-schema-linter --old-implements-syntax --comment-descriptions cmd/frontend/graphqlbackend/schema.graphql",
|
||||
"prepublish": "gulp generate",
|
||||
"test": "jest --testPathIgnorePatterns e2e regression integration storybook",
|
||||
"test-e2e": "TS_NODE_PROJECT=web/src/e2e/tsconfig.json mocha ./web/src/e2e/e2e.test.ts",
|
||||
"test": "jest --testPathIgnorePatterns end-to-end regression integration storybook",
|
||||
"test-e2e": "TS_NODE_PROJECT=web/src/end-to-end/tsconfig.json mocha ./web/src/end-to-end/end-to-end.test.ts",
|
||||
"cover-e2e": "nyc --hook-require=false yarn test-e2e",
|
||||
"storybook": "start-storybook -p 9001 -c .storybook",
|
||||
"build-storybook": "build-storybook -c .storybook",
|
||||
@ -81,7 +81,7 @@
|
||||
"@percy/storybook": "^3.3.0",
|
||||
"@slack/web-api": "^5.8.1",
|
||||
"@sourcegraph/babel-plugin-transform-react-hot-loader-wrapper": "^1.0.0",
|
||||
"@sourcegraph/eslint-config": "^0.18.4",
|
||||
"@sourcegraph/eslint-config": "^0.19.1",
|
||||
"@sourcegraph/prettierrc": "^3.0.3",
|
||||
"@sourcegraph/stylelint-config": "^1.1.9",
|
||||
"@sourcegraph/tsconfig": "^4.0.1",
|
||||
|
||||
@ -1 +1,2 @@
|
||||
out/
|
||||
src/graphql/schema.ts
|
||||
|
||||
@ -3,7 +3,7 @@ module.exports = {
|
||||
extends: '../.eslintrc.js',
|
||||
parserOptions: {
|
||||
...baseConfig.parserOptions,
|
||||
project: [__dirname + '/tsconfig.json', __dirname + '/src/e2e/tsconfig.json'],
|
||||
project: [__dirname + '/tsconfig.json', __dirname + '/src/testing/tsconfig.json'],
|
||||
},
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
|
||||
@ -120,7 +120,7 @@ describe('ActionItem', () => {
|
||||
// Finish execution. (Use setTimeout to wait for the executeCommand resolution to result in the setState
|
||||
// call.)
|
||||
done()
|
||||
await new Promise<void>(r => setTimeout(r))
|
||||
await new Promise<void>(resolve => setTimeout(resolve))
|
||||
tree = component.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
@ -149,7 +149,7 @@ describe('ActionItem', () => {
|
||||
// Finish execution. (Use setTimeout to wait for the executeCommand resolution to result in the setState
|
||||
// call.)
|
||||
done()
|
||||
await new Promise<void>(r => setTimeout(r))
|
||||
await new Promise<void>(resolve => setTimeout(resolve))
|
||||
tree = component.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
@ -174,7 +174,7 @@ describe('ActionItem', () => {
|
||||
// to result in the setState call.)
|
||||
let tree = component.toJSON()
|
||||
tree!.props.onClick({ preventDefault: () => undefined, currentTarget: { blur: () => undefined } })
|
||||
await new Promise<void>(r => setTimeout(r))
|
||||
await new Promise<void>(resolve => setTimeout(resolve))
|
||||
tree = component.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
@ -199,7 +199,7 @@ describe('ActionItem', () => {
|
||||
// to result in the setState call.)
|
||||
let tree = component.toJSON()
|
||||
tree!.props.onClick({ preventDefault: () => undefined, currentTarget: { blur: () => undefined } })
|
||||
await new Promise<void>(r => setTimeout(r))
|
||||
await new Promise<void>(resolve => setTimeout(resolve))
|
||||
tree = component.toJSON()
|
||||
expect(tree).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@ -95,11 +95,13 @@ export class ActionItem extends React.PureComponent<ActionItemProps, State> {
|
||||
this.subscriptions.add(
|
||||
this.commandExecutions
|
||||
.pipe(
|
||||
mergeMap(params =>
|
||||
from(this.props.extensionsController.executeCommand(params, this.props.showInlineError)).pipe(
|
||||
mergeMap(parameters =>
|
||||
from(
|
||||
this.props.extensionsController.executeCommand(parameters, this.props.showInlineError)
|
||||
).pipe(
|
||||
mapTo(null),
|
||||
catchError(error => [asError(error)]),
|
||||
map(c => ({ actionOrError: c })),
|
||||
map(actionOrError => ({ actionOrError })),
|
||||
tap(() => {
|
||||
if (this.props.onDidExecute) {
|
||||
this.props.onDidExecute(this.props.action.id)
|
||||
@ -116,18 +118,18 @@ export class ActionItem extends React.PureComponent<ActionItemProps, State> {
|
||||
)
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: ActionItemProps, prevState: State): void {
|
||||
public componentDidUpdate(previousProps: ActionItemProps, previousState: State): void {
|
||||
// If the tooltip changes while it's visible, we need to force-update it to show the new value.
|
||||
const prevTooltip = prevProps.action.actionItem && prevProps.action.actionItem.description
|
||||
const previousTooltip = previousProps.action.actionItem && previousProps.action.actionItem.description
|
||||
const tooltip = this.props.action.actionItem && this.props.action.actionItem.description
|
||||
const descriptionTooltipChanged = prevTooltip !== tooltip
|
||||
const descriptionTooltipChanged = previousTooltip !== tooltip
|
||||
|
||||
const errorTooltipChanged =
|
||||
this.props.showInlineError &&
|
||||
(isErrorLike(prevState.actionOrError) !== isErrorLike(this.state.actionOrError) ||
|
||||
(isErrorLike(prevState.actionOrError) &&
|
||||
(isErrorLike(previousState.actionOrError) !== isErrorLike(this.state.actionOrError) ||
|
||||
(isErrorLike(previousState.actionOrError) &&
|
||||
isErrorLike(this.state.actionOrError) &&
|
||||
prevState.actionOrError.message !== this.state.actionOrError.message))
|
||||
previousState.actionOrError.message !== this.state.actionOrError.message))
|
||||
|
||||
if (descriptionTooltipChanged || errorTooltipChanged) {
|
||||
this.props.platformContext.forceUpdateTooltip()
|
||||
@ -239,8 +241,8 @@ export class ActionItem extends React.PureComponent<ActionItemProps, State> {
|
||||
)
|
||||
}
|
||||
|
||||
public runAction = (e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>): void => {
|
||||
const action = (isAltEvent(e) && this.props.altAction) || this.props.action
|
||||
public runAction = (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>): void => {
|
||||
const action = (isAltEvent(event) && this.props.altAction) || this.props.action
|
||||
|
||||
if (!action.command) {
|
||||
// Unexpectedly arrived here; noop actions should not have event handlers that trigger
|
||||
@ -252,7 +254,7 @@ export class ActionItem extends React.PureComponent<ActionItemProps, State> {
|
||||
this.props.telemetryService.log(action.id)
|
||||
|
||||
if (urlForClientCommandOpen(action, this.props.location)) {
|
||||
if (e.currentTarget.tagName === 'A' && e.currentTarget.hasAttribute('href')) {
|
||||
if (event.currentTarget.tagName === 'A' && event.currentTarget.hasAttribute('href')) {
|
||||
// Do not execute the command. The <LinkOrButton>'s default event handler will do what we want (which
|
||||
// is to open a URL). The only case where this breaks is if both the action and alt action are "open"
|
||||
// commands; in that case, this only ever opens the (non-alt) action.
|
||||
@ -270,10 +272,10 @@ export class ActionItem extends React.PureComponent<ActionItemProps, State> {
|
||||
|
||||
// If the action we're running is *not* opening a URL by using the event target's default handler, then
|
||||
// ensure the default event handler for the <LinkOrButton> doesn't run (which might open the URL).
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
|
||||
// Do not show focus ring on element after running action.
|
||||
e.currentTarget.blur()
|
||||
event.currentTarget.blur()
|
||||
|
||||
this.commandExecutions.next({
|
||||
command: action.command,
|
||||
@ -302,6 +304,6 @@ function urlForClientCommandOpen(action: Evaluated<ActionContribution>, location
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isAltEvent(e: React.KeyboardEvent | React.MouseEvent): boolean {
|
||||
return e.altKey || e.metaKey || e.ctrlKey || ('button' in e && e.button === 1)
|
||||
function isAltEvent(event: React.KeyboardEvent | React.MouseEvent): boolean {
|
||||
return event.altKey || event.metaKey || event.ctrlKey || ('button' in event && event.button === 1)
|
||||
}
|
||||
|
||||
@ -55,11 +55,11 @@ export class ActionsContainer extends React.PureComponent<Props, ActionsState> {
|
||||
this.scopeChanges.next(this.props.scope)
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props): void {
|
||||
if (prevProps.scope !== this.props.scope) {
|
||||
public componentDidUpdate(previousProps: Props): void {
|
||||
if (previousProps.scope !== this.props.scope) {
|
||||
this.scopeChanges.next(this.props.scope)
|
||||
}
|
||||
if (prevProps.extraContext !== this.props.extraContext) {
|
||||
if (previousProps.extraContext !== this.props.extraContext) {
|
||||
this.extraContextChanges.next(this.props.extraContext)
|
||||
}
|
||||
}
|
||||
@ -84,8 +84,8 @@ export class ActionsContainer extends React.PureComponent<Props, ActionsState> {
|
||||
|
||||
private defaultRenderItems = (items: ActionItemAction[]): JSX.Element | null => (
|
||||
<>
|
||||
{items.map((item, i) => (
|
||||
<ActionItem {...this.props} key={i} {...item} />
|
||||
{items.map((item, index) => (
|
||||
<ActionItem {...this.props} key={index} {...item} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
@ -71,11 +71,11 @@ export class ActionsNavItems extends React.PureComponent<ActionsNavItemsProps, A
|
||||
this.extraContextChanges.next(this.props.extraContext)
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: ActionsProps): void {
|
||||
if (prevProps.scope !== this.props.scope) {
|
||||
public componentDidUpdate(previousProps: ActionsProps): void {
|
||||
if (previousProps.scope !== this.props.scope) {
|
||||
this.scopeChanges.next(this.props.scope)
|
||||
}
|
||||
if (prevProps.extraContext !== this.props.extraContext) {
|
||||
if (previousProps.extraContext !== this.props.extraContext) {
|
||||
this.extraContextChanges.next(this.props.extraContext)
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,9 +21,11 @@ import { FeatureProviderRegistry } from '../services/registry'
|
||||
export class ProxySubscription extends Subscription {
|
||||
constructor(proxy: Pick<ProxyMethods, typeof releaseProxy>) {
|
||||
super(() => {
|
||||
const p = proxy
|
||||
;(proxy as any) = null // null out closure reference to proxy
|
||||
p[releaseProxy]()
|
||||
proxy[releaseProxy]()
|
||||
|
||||
// Workaround for https://github.com/ReactiveX/rxjs/issues/5464
|
||||
// Remove when fixed
|
||||
;(this as any)._unsubscribe = null
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -76,15 +78,15 @@ export const wrapRemoteObservable = <T>(
|
||||
proxyObserver = {
|
||||
[proxyMarker]: true,
|
||||
next: args[0] || noop,
|
||||
error: args[1] ? err => args[1](asError(err)) : noop,
|
||||
error: args[1] ? error => args[1](asError(error)) : noop,
|
||||
complete: args[2] || noop,
|
||||
}
|
||||
} else {
|
||||
const partialObserver = args[0] || {}
|
||||
proxyObserver = {
|
||||
[proxyMarker]: true,
|
||||
next: partialObserver.next ? val => partialObserver.next(val) : noop,
|
||||
error: partialObserver.error ? err => partialObserver.error(asError(err)) : noop,
|
||||
next: partialObserver.next ? value => partialObserver.next(value) : noop,
|
||||
error: partialObserver.error ? error => partialObserver.error(asError(error)) : noop,
|
||||
complete: partialObserver.complete ? () => partialObserver.complete() : noop,
|
||||
}
|
||||
}
|
||||
@ -142,11 +144,11 @@ export function registerRemoteProvider<
|
||||
const subscription = new Subscription()
|
||||
|
||||
subscription.add(
|
||||
registry.registerProvider(registrationOptions, params =>
|
||||
registry.registerProvider(registrationOptions, parameters =>
|
||||
// Wrap the remote, proxied Observable in an ordinary Observable
|
||||
// and add its underlying proxy subscription to our subscription
|
||||
// to release the proxy when the provider gets unregistered.
|
||||
wrapRemoteObservable(remoteProviderFunction(params), subscription)
|
||||
wrapRemoteObservable(remoteProviderFunction(parameters), subscription)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -25,15 +25,15 @@ export class ClientExtensions {
|
||||
const toDeactivate: ExecutableExtension[] = []
|
||||
const next: ExecutableExtension[] = []
|
||||
if (oldExtensions) {
|
||||
for (const x of oldExtensions) {
|
||||
const newIndex = toActivate.findIndex(({ id }) => x.id === id)
|
||||
for (const extension of oldExtensions) {
|
||||
const newIndex = toActivate.findIndex(({ id }) => extension.id === id)
|
||||
if (newIndex === -1) {
|
||||
// Extension is no longer activated
|
||||
toDeactivate.push(x)
|
||||
toDeactivate.push(extension)
|
||||
} else {
|
||||
// Extension is already activated.
|
||||
toActivate.splice(newIndex, 1)
|
||||
next.push(x)
|
||||
next.push(extension)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,16 +43,16 @@ export class ClientExtensions {
|
||||
* {@link activeExtensions} never deactivates extensions, so this will never be
|
||||
* called (in the current implementation).
|
||||
*/
|
||||
for (const x of toDeactivate) {
|
||||
this.proxy.$deactivateExtension(x.id).catch(error => {
|
||||
console.warn(`Error deactivating extension ${JSON.stringify(x.id)}:`, error)
|
||||
for (const extension of toDeactivate) {
|
||||
this.proxy.$deactivateExtension(extension.id).catch(error => {
|
||||
console.warn(`Error deactivating extension ${JSON.stringify(extension.id)}:`, error)
|
||||
})
|
||||
}
|
||||
|
||||
// Activate extensions that haven't yet been activated.
|
||||
for (const x of toActivate) {
|
||||
this.proxy.$activateExtension(x.id, x.scriptURL).catch(error => {
|
||||
console.error(`Error activating extension ${JSON.stringify(x.id)}:`, error)
|
||||
for (const extension of toActivate) {
|
||||
this.proxy.$activateExtension(extension.id, extension.scriptURL).catch(error => {
|
||||
console.error(`Error activating extension ${JSON.stringify(extension.id)}:`, error)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -61,7 +61,7 @@ export class ClientViews implements ClientViewsAPI {
|
||||
combineLatest([
|
||||
panelView.pipe(
|
||||
map(data => omit(data, 'component')),
|
||||
distinctUntilChanged((x, y) => isEqual(x, y))
|
||||
distinctUntilChanged((a, b) => isEqual(a, b))
|
||||
),
|
||||
panelView.pipe(
|
||||
map(({ component }) => component),
|
||||
@ -74,11 +74,14 @@ export class ClientViews implements ClientViewsAPI {
|
||||
return from(this.viewerService.activeViewerUpdates).pipe(
|
||||
map(getActiveCodeEditorPosition),
|
||||
switchMap(
|
||||
(params): ObservableInput<MaybeLoadingResult<Location[]>> => {
|
||||
if (!params) {
|
||||
(parameters): ObservableInput<MaybeLoadingResult<Location[]>> => {
|
||||
if (!parameters) {
|
||||
return [{ isLoading: false, result: [] }]
|
||||
}
|
||||
return this.textDocumentLocations.getLocations(component.locationProvider, params)
|
||||
return this.textDocumentLocations.getLocations(
|
||||
component.locationProvider,
|
||||
parameters
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@ -54,10 +54,10 @@ export class ClientWindows implements ClientWindowsAPI {
|
||||
return this.showInput({
|
||||
message: options?.prompt ? options.prompt : '',
|
||||
defaultValue: options?.value,
|
||||
}).then(v =>
|
||||
}).then(input =>
|
||||
// TODO(sqs): update the showInput API to unify null/undefined etc between the old internal API and the new
|
||||
// external API.
|
||||
v === null ? undefined : v
|
||||
input === null ? undefined : input
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -97,14 +97,14 @@ export async function createExtensionHostClientConnection(
|
||||
)
|
||||
|
||||
const clientWindows = new ClientWindows(
|
||||
(params: ShowNotificationParams) => services.notifications.showMessages.next({ ...params }),
|
||||
(params: ShowMessageRequestParams) =>
|
||||
(parameters: ShowNotificationParams) => services.notifications.showMessages.next({ ...parameters }),
|
||||
(parameters: ShowMessageRequestParams) =>
|
||||
new Promise<MessageActionItem | null>(resolve => {
|
||||
services.notifications.showMessageRequests.next({ ...params, resolve })
|
||||
services.notifications.showMessageRequests.next({ ...parameters, resolve })
|
||||
}),
|
||||
(params: ShowInputParams) =>
|
||||
(parameters: ShowInputParams) =>
|
||||
new Promise<string | null>(resolve => {
|
||||
services.notifications.showInputs.next({ ...params, resolve })
|
||||
services.notifications.showInputs.next({ ...parameters, resolve })
|
||||
}),
|
||||
({ title }: ProgressOptions) => {
|
||||
const reporter = new Subject<Progress>()
|
||||
|
||||
@ -70,26 +70,30 @@ describe('getComputedContextProperty', () => {
|
||||
test('returns null when there are no code editors', () =>
|
||||
expect(getComputedContextProperty(undefined, EMPTY_SETTINGS_CASCADE, {}, 'component.type')).toBe(null))
|
||||
|
||||
function assertSelection(editor: CodeEditorWithPartialModel, expr: string, expected: Selection): void {
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, expr)).toEqual(expected)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expr}.start`)).toEqual(
|
||||
function assertSelection(
|
||||
editor: CodeEditorWithPartialModel,
|
||||
expression: string,
|
||||
expected: Selection
|
||||
): void {
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, expression)).toEqual(expected)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expression}.start`)).toEqual(
|
||||
expected.start
|
||||
)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expr}.end`)).toEqual(
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expression}.end`)).toEqual(
|
||||
expected.end
|
||||
)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expr}.start.line`)).toBe(
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expression}.start.line`)).toBe(
|
||||
expected.start.line
|
||||
)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expr}.start.character`)).toBe(
|
||||
expected.start.character
|
||||
)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expr}.end.line`)).toBe(
|
||||
expect(
|
||||
getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expression}.start.character`)
|
||||
).toBe(expected.start.character)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expression}.end.line`)).toBe(
|
||||
expected.end.line
|
||||
)
|
||||
expect(getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expr}.end.character`)).toBe(
|
||||
expected.end.character
|
||||
)
|
||||
expect(
|
||||
getComputedContextProperty(editor, EMPTY_SETTINGS_CASCADE, {}, `${expression}.end.character`)
|
||||
).toBe(expected.end.character)
|
||||
}
|
||||
|
||||
test('provides primary selection', () =>
|
||||
|
||||
@ -38,8 +38,8 @@ export function getComputedContextProperty(
|
||||
scope?: ContributionScope
|
||||
): any {
|
||||
if (key.startsWith('config.')) {
|
||||
const prop = key.slice('config.'.length)
|
||||
const value = isSettingsValid(settings) ? settings.final[prop] : undefined
|
||||
const property = key.slice('config.'.length)
|
||||
const value = isSettingsValid(settings) ? settings.final[property] : undefined
|
||||
// Map undefined to null because an undefined value is treated as "does not exist in
|
||||
// context" and an error is thrown, which is undesirable for config values (for
|
||||
// which a falsey null default is useful).
|
||||
@ -56,8 +56,8 @@ export function getComputedContextProperty(
|
||||
// TODO(sqs): Define these precisely. If the resource is in a repository, what is the "path"? Is it the
|
||||
// path relative to the repository's root? If it's a file on disk, then "path" could also mean the
|
||||
// (absolute) path on the file system. Clear up that ambiguity.
|
||||
const prop = key.slice('resource.'.length)
|
||||
switch (prop) {
|
||||
const property = key.slice('resource.'.length)
|
||||
switch (property) {
|
||||
case 'uri':
|
||||
return component.resource
|
||||
case 'basename':
|
||||
@ -76,8 +76,8 @@ export function getComputedContextProperty(
|
||||
if (component?.type !== 'CodeEditor') {
|
||||
return null
|
||||
}
|
||||
const prop = key.slice('component.'.length)
|
||||
switch (prop) {
|
||||
const property = key.slice('component.'.length)
|
||||
switch (property) {
|
||||
case 'type':
|
||||
return 'CodeEditor'
|
||||
case 'selections':
|
||||
@ -102,8 +102,8 @@ export function getComputedContextProperty(
|
||||
if (component?.type !== 'panelView') {
|
||||
return null
|
||||
}
|
||||
const prop = key.slice('panel.activeView.'.length)
|
||||
switch (prop) {
|
||||
const property = key.slice('panel.activeView.'.length)
|
||||
switch (property) {
|
||||
case 'id':
|
||||
return component.id
|
||||
case 'hasLocations':
|
||||
|
||||
@ -41,9 +41,9 @@ describe('Expression', () => {
|
||||
'a || isnotdefined': 1, // short-circuit (if not, the use of an undefined ident would cause an error)
|
||||
}
|
||||
/* eslint-enable no-template-curly-in-string */
|
||||
for (const [expr, want] of Object.entries(TESTS)) {
|
||||
test(expr, () => {
|
||||
const value = parse<unknown>(expr).exec(FIXTURE_CONTEXT)
|
||||
for (const [expression, want] of Object.entries(TESTS)) {
|
||||
test(expression, () => {
|
||||
const value = parse<unknown>(expression).exec(FIXTURE_CONTEXT)
|
||||
expect(value).toBe(want)
|
||||
})
|
||||
}
|
||||
|
||||
@ -21,8 +21,8 @@ export class TemplateExpression<S extends string = string> extends Expression<S>
|
||||
/**
|
||||
* Evaluates an expression with the given context and returns the result.
|
||||
*/
|
||||
export function parse<T>(expr: string): Expression<T> {
|
||||
return new Expression<T>(new Parser().parse(expr))
|
||||
export function parse<T>(expression: string): Expression<T> {
|
||||
return new Expression<T>(new Parser().parse(expression))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,8 +46,8 @@ export const EMPTY_COMPUTED_CONTEXT: ComputedContext = {
|
||||
}
|
||||
|
||||
const FUNCS: { [name: string]: (...args: any[]) => any } = {
|
||||
get: (obj: any, key: string): any => obj?.[key] ?? undefined,
|
||||
json: (obj: any): string => JSON.stringify(obj),
|
||||
get: (object: any, key: string): any => object?.[key] ?? undefined,
|
||||
json: (object: any): string => JSON.stringify(object),
|
||||
}
|
||||
|
||||
function exec(node: ExpressionNode, context: ComputedContext): any {
|
||||
@ -64,8 +64,8 @@ function exec(node: ExpressionNode, context: ComputedContext): any {
|
||||
|
||||
if ('Template' in node) {
|
||||
const parts: any[] = []
|
||||
for (const expr of node.Template.parts) {
|
||||
parts.push(exec(expr, context))
|
||||
for (const expression of node.Template.parts) {
|
||||
parts.push(exec(expression, context))
|
||||
}
|
||||
return parts.join('')
|
||||
}
|
||||
@ -114,14 +114,14 @@ function exec(node: ExpressionNode, context: ComputedContext): any {
|
||||
}
|
||||
|
||||
if ('Unary' in node) {
|
||||
const expr = exec(node.Unary.expression, context)
|
||||
const expression = exec(node.Unary.expression, context)
|
||||
switch (node.Unary.operator) {
|
||||
case '!':
|
||||
return !expr
|
||||
return !expression
|
||||
case '+':
|
||||
return expr
|
||||
return expression
|
||||
case '-':
|
||||
return -expr
|
||||
return -expression
|
||||
default:
|
||||
throw new SyntaxError(`Invalid operator: ${node.Unary.operator}`)
|
||||
}
|
||||
@ -142,13 +142,13 @@ function exec(node: ExpressionNode, context: ComputedContext): any {
|
||||
}
|
||||
|
||||
if ('FunctionCall' in node) {
|
||||
const expr = node.FunctionCall
|
||||
const func = FUNCS[expr.name]
|
||||
const expression = node.FunctionCall
|
||||
const func = FUNCS[expression.name]
|
||||
if (typeof func === 'function') {
|
||||
const args = expr.args.map(arg => exec(arg, context))
|
||||
const args = expression.args.map(argument => exec(argument, context))
|
||||
return func(...args)
|
||||
}
|
||||
throw new SyntaxError(`Undefined function: ${expr.name}`)
|
||||
throw new SyntaxError(`Undefined function: ${expression.name}`)
|
||||
}
|
||||
|
||||
throw new SyntaxError('Unrecognized syntax node')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user