Implement a no sync rule (#7216)

* implement a no sync rule

* fix linting disable

* fix unused imports

* exclude more testing

* clean up fs usage

* clean up more fs usage

* remove duplicate of code

* fix compile errors
This commit is contained in:
Anthony Dresser
2019-09-17 13:32:42 -07:00
committed by GitHub
parent 4d62983680
commit 28d453fced
31 changed files with 305 additions and 201 deletions

View File

@@ -0,0 +1,33 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
const minimatch = require("minimatch");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
const args = this.getOptions().ruleArguments[0];
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
exports.Rule = Rule;
class NoSyncRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
}
visitCallExpression(node) {
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
this.addFailureAtNode(node, `Do not use Sync operations`);
}
super.visitCallExpression(node);
}
}
NoSyncRuleWalker.operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as minimatch from 'minimatch';
interface NoSyncRuleConfig {
exclude: string[];
}
export class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const args = <NoSyncRuleConfig>this.getOptions().ruleArguments[0];
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
class NoSyncRuleWalker extends Lint.RuleWalker {
private static readonly operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
visitCallExpression(node: ts.CallExpression) {
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
this.addFailureAtNode(node, `Do not use Sync operations`);
}
super.visitCallExpression(node);
}
}

View File

@@ -10,8 +10,7 @@ import * as vscode from 'vscode';
import { TelemetryReporter, TelemetryViews } from './telemetry';
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils';
import { ChildProcess, exec } from 'child_process';
import { promisify } from 'util';
import { readFile } from 'fs';
import { promises as fs } from 'fs';
const localize = nls.loadMessageBundle();
@@ -68,7 +67,7 @@ export interface LaunchSsmsDialogParams {
export async function activate(context: vscode.ExtensionContext): Promise<void> {
// This is for Windows-specific support so do nothing on other platforms
if (process.platform === 'win32') {
const rawConfig = await promisify(readFile)(path.join(context.extensionPath, 'config.json'));
const rawConfig = await fs.readFile(path.join(context.extensionPath, 'config.json'));
const ssmsMinVer = JSON.parse(rawConfig.toString()).version;
exePath = path.join(context.extensionPath, 'ssmsmin', 'Windows', ssmsMinVer, 'ssmsmin.exe');
registerCommands(context);

View File

@@ -6,9 +6,7 @@
'use strict';
import * as azdata from 'azdata';
import * as fs from 'fs';
import { promisify } from 'util';
import { promises as fs } from 'fs';
export class AgentUtils {
@@ -52,18 +50,11 @@ export class AgentUtils {
}
export function exists(path: string): Promise<boolean> {
return promisify(fs.exists)(path);
}
export function mkdir(path: string): Promise<void> {
return promisify(fs.mkdir)(path);
}
export function unlink(path: string): Promise<void> {
return promisify(fs.unlink)(path);
}
export function writeFile(path: string, data: string): Promise<void> {
return promisify(fs.writeFile)(path, data);
export async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -7,12 +7,10 @@
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { AgentUtils } from '../agentUtils';
import { AgentUtils, exists } from '../agentUtils';
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
import { NotebookDialogOptions } from '../dialogs/notebookDialog';
import { createConnection } from 'net';
const localize = nls.loadMessageBundle();
const NotebookCompletionActionCondition_Always: string = localize('notebookData.whenJobCompletes', 'When the notebook completes');
@@ -179,7 +177,7 @@ export class NotebookData implements IAgentDialogData {
}
}
public validate(): { valid: boolean, errorMessages: string[] } {
public async validate(): Promise<{ valid: boolean, errorMessages: string[] }> {
let validationErrors: string[] = [];
if (this.dialogMode !== AgentDialogMode.EDIT) {
if (!(this.name && this.name.trim())) {
@@ -188,7 +186,7 @@ export class NotebookData implements IAgentDialogData {
if (!(this.templatePath && this.name.trim())) {
validationErrors.push(TemplatePathEmptyErrorMessage);
}
if (!fs.existsSync(this.templatePath)) {
if (!(await exists(this.templatePath))) {
validationErrors.push(InvalidNotebookPathErrorMessage);
}
if (NotebookData.jobLists) {
@@ -201,7 +199,7 @@ export class NotebookData implements IAgentDialogData {
}
}
else {
if (this.templatePath && this.templatePath !== '' && !fs.existsSync(this.templatePath)) {
if (this.templatePath && this.templatePath !== '' && !(await exists(this.templatePath))) {
validationErrors.push(InvalidNotebookPathErrorMessage);
}
}

View File

@@ -87,9 +87,9 @@ export class NotebookDialog extends AgentDialog<NotebookData> {
this.generalTab = azdata.window.createTab(GeneralTabText);
this.initializeGeneralTab();
this.dialog.content = [this.generalTab];
this.dialog.registerCloseValidator(() => {
this.dialog.registerCloseValidator(async () => {
this.updateModel();
let validationResult = this.model.validate();
let validationResult = await this.model.validate();
if (!validationResult.valid) {
// TODO: Show Error Messages
this.dialog.message = { text: validationResult.errorMessages[0] };

View File

@@ -7,7 +7,7 @@
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { AlertDialog } from './dialogs/alertDialog';
@@ -17,9 +17,8 @@ import { ProxyDialog } from './dialogs/proxyDialog';
import { JobStepDialog } from './dialogs/jobStepDialog';
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
import { JobData } from './data/jobData';
import { AgentUtils, exists, mkdir, unlink, writeFile } from './agentUtils';
import { AgentUtils, exists } from './agentUtils';
import { NotebookDialog, NotebookDialogOptions } from './dialogs/notebookDialog';
import { promisify } from 'util';
const localize = nls.loadMessageBundle();
@@ -102,7 +101,7 @@ export class MainController {
let templateMap = this.notebookTemplateMap.get(nbEditor.document.uri.toString());
let vsEditor = await vscode.workspace.openTextDocument(templateMap.fileUri);
let content = vsEditor.getText();
promisify(fs.writeFile)(templateMap.tempPath, content);
await fs.writeFile(templateMap.tempPath, content);
AgentUtils.getAgentService().then(async (agentService) => {
let result = await agentService.updateNotebook(templateMap.ownerUri, templateMap.notebookInfo.name, templateMap.notebookInfo, templateMap.tempPath);
if (result.success) {
@@ -128,14 +127,13 @@ export class MainController {
vscode.commands.registerCommand('agent.openNotebookEditorFromJsonString', async (filename: string, jsonNotebook: string, notebookInfo?: azdata.AgentNotebookInfo, ownerUri?: string) => {
const tempfilePath = path.join(os.tmpdir(), 'mssql_notebooks', filename + '.ipynb');
if (!await exists(path.join(os.tmpdir(), 'mssql_notebooks'))) {
await mkdir(path.join(os.tmpdir(), 'mssql_notebooks'));
await fs.mkdir(path.join(os.tmpdir(), 'mssql_notebooks'));
}
let editors = azdata.nb.visibleNotebookEditors;
if (await exists(tempfilePath)) {
await unlink(tempfilePath);
await fs.unlink(tempfilePath);
}
try {
await writeFile(tempfilePath, jsonNotebook);
await fs.writeFile(tempfilePath, jsonNotebook);
let uri = vscode.Uri.parse(`untitled:${path.basename(tempfilePath)}`);
if (notebookInfo) {
this.notebookTemplateMap.set(uri.toString(), { notebookInfo: notebookInfo, fileUri: uri, ownerUri: ownerUri, tempPath: tempfilePath });

View File

@@ -6,7 +6,7 @@
import * as adal from 'adal-node';
import * as azdata from 'azdata';
import * as crypto from 'crypto';
import * as fs from 'fs';
import { promises as fs } from 'fs';
export default class TokenCache implements adal.TokenCache {
private static CipherAlgorithm = 'aes-256-cbc';
@@ -58,23 +58,19 @@ export default class TokenCache implements adal.TokenCache {
});
}
public clear(): Thenable<void> {
let self = this;
public async clear(): Promise<void> {
// 1) Delete encrypted serialization file
// If we got an 'ENOENT' response, the file doesn't exist, which is fine
// 3) Delete the encryption key
return new Promise<void>((resolve, reject) => {
fs.unlink(self._cacheSerializationPath, err => {
if (err && err.code !== 'ENOENT') {
reject(err);
} else {
resolve();
}
});
})
.then(() => { return self._credentialProvider.deleteCredential(self._credentialServiceKey); })
.then(() => { });
try {
await fs.unlink(this._cacheSerializationPath);
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
await this._credentialProvider.deleteCredential(this._credentialServiceKey);
}
public find(query: any, callback: (error: Error, results: any[]) => void): void {
@@ -230,7 +226,7 @@ export default class TokenCache implements adal.TokenCache {
});
}
private readCache(): Thenable<adal.TokenResponse[]> {
private async readCache(): Promise<adal.TokenResponse[]> {
let self = this;
// NOTE: File system operations are performed synchronously to avoid annoying nested callbacks
@@ -239,13 +235,13 @@ export default class TokenCache implements adal.TokenCache {
// 3) Decrypt the file contents
// 4) Deserialize and return
return this.getOrCreateEncryptionParams()
.then(encryptionParams => {
.then(async encryptionParams => {
try {
return self.decryptCache('utf8', encryptionParams);
} catch (e) {
try {
// try to parse using 'binary' encoding and rewrite cache as UTF8
let response = self.decryptCache('binary', encryptionParams);
let response = await self.decryptCache('binary', encryptionParams);
self.writeCache(response);
return response;
} catch (e) {
@@ -260,17 +256,17 @@ export default class TokenCache implements adal.TokenCache {
});
}
private decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): adal.TokenResponse[] {
let cacheCipher = fs.readFileSync(this._cacheSerializationPath, TokenCache.FsOptions);
private async decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): Promise<adal.TokenResponse[]> {
let cacheCipher = await fs.readFile(this._cacheSerializationPath, TokenCache.FsOptions);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', encoding);
let cacheJson = decipher.update(cacheCipher.toString(), 'hex', encoding);
cacheJson += decipher.final(encoding);
// Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) {
for (const obj of cacheObj) {
// Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
obj.expiresOn = new Date(<string>obj.expiresOn);
}
return cacheObj;
@@ -297,7 +293,7 @@ export default class TokenCache implements adal.TokenCache {
// 4) Encrypt the JSON
// 3) Write to the file
return this.getOrCreateEncryptionParams()
.then(encryptionParams => {
.then(async encryptionParams => {
try {
let cacheJson = JSON.stringify(cache);
@@ -305,7 +301,7 @@ export default class TokenCache implements adal.TokenCache {
let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex');
cacheCipher += cipher.final('hex');
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
await fs.writeFile(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
} catch (e) {
throw e;
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as constants from './constants';
@@ -54,12 +54,12 @@ function pushDisposable(disposable: vscode.Disposable): void {
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
export async function activate(context: vscode.ExtensionContext) {
extensionContext = context;
const apiWrapper = new ApiWrapper();
let appContext = new AppContext(extensionContext, apiWrapper);
let storagePath = findOrMakeStoragePath();
let storagePath = await findOrMakeStoragePath();
if (!storagePath) {
return undefined;
}
@@ -82,12 +82,21 @@ export function activate(context: vscode.ExtensionContext) {
};
}
async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
// Create the folder for storing the token caches
function findOrMakeStoragePath() {
async function findOrMakeStoragePath() {
let storagePath = path.join(getDefaultLogLocation(), constants.extensionName);
try {
if (!fs.existsSync(storagePath)) {
fs.mkdirSync(storagePath);
if (!(await exists(storagePath))) {
await fs.mkdir(storagePath);
console.log('Initialized Azure account extension storage.');
}
}
@@ -124,4 +133,3 @@ function registerCommands(appContext: AppContext, azureResourceTree: AzureResour
registerAzureResourceDatabaseCommands(appContext);
}

View File

@@ -6,7 +6,7 @@
'use strict';
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
import { IConfig, ServerProvider, Events } from 'service-downloader';
import { ServerProvider, Events } from 'service-downloader';
import { ServerOptions, TransportKind } from 'vscode-languageclient';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
@@ -18,8 +18,7 @@ import { Telemetry, LanguageClientErrorHandler } from './telemetry';
import * as Constants from '../constants';
import { TelemetryFeature, FlatFileImportFeature } from './features';
import * as serviceUtils from './serviceUtils';
import { promisify } from 'util';
import { readFile } from 'fs';
import { promises as fs } from 'fs';
export class ServiceClient {
private statusView: vscode.StatusBarItem;
@@ -29,7 +28,7 @@ export class ServiceClient {
}
public async startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
const rawConfig = await promisify(readFile)(path.join(context.extensionPath, 'config.json'));
const rawConfig = await fs.readFile(path.join(context.extensionPath, 'config.json'));
const config = JSON.parse(rawConfig.toString());
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');

View File

@@ -28,6 +28,7 @@ import { registerBooksWidget } from './dashboard/bookWidget';
import { createMssqlApi } from './mssqlApiFactory';
import { localize } from './localize';
import { SqlToolsServer } from './sqlToolsServer';
import { promises as fs } from 'fs';
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
@@ -42,8 +43,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
}
// ensure our log path exists
if (!(await Utils.pfs.exists(context.logPath))) {
await Utils.pfs.mkdir(context.logPath);
if (!(await Utils.exists(context.logPath))) {
await fs.mkdir(context.logPath);
}
let prompter: IPrompter = new CodeAdapter();
@@ -200,7 +201,7 @@ async function handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promi
async function handleOpenClusterStatusNotebookTask(profile: azdata.IConnectionProfile, appContext: AppContext): Promise<void> {
const notebookRelativePath: string = 'notebooks/tsg/cluster-status.ipynb';
const notebookFullPath: string = path.join(appContext.extensionContext.extensionPath, notebookRelativePath);
if (!Utils.fileExists(notebookFullPath)) {
if (!(await Utils.exists(notebookFullPath))) {
vscode.window.showErrorMessage(localize("fileNotFound", "Unable to find the file specified"));
} else {
const title: string = Utils.findNextUntitledEditorName(notebookFullPath);

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as fspath from 'path';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
@@ -22,7 +22,7 @@ import { AppContext } from '../appContext';
import { TreeNode } from './treeNodes';
import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider';
function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: boolean): vscode.Uri {
async function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: boolean): Promise<vscode.Uri> {
let root = utils.getUserHome();
let workspaceFolders = apiWrapper.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
@@ -33,7 +33,7 @@ function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: bo
let fileNum = 1;
let fileNameWithoutExtension = fspath.parse(fileName).name;
let fileExtension = fspath.parse(fileName).ext;
while (fs.existsSync(fspath.join(root, fileName))) {
while (await utils.exists(fspath.join(root, fileName))) {
fileName = `${fileNameWithoutExtension}-${fileNum}${fileExtension}`;
fileNum++;
}
@@ -82,7 +82,7 @@ export class UploadFilesCommand extends ProgressCommand {
};
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
if (fileUris) {
let files: IFile[] = fileUris.map(uri => uri.fsPath).map(this.mapPathsToFiles());
let files: IFile[] = await Promise.all(fileUris.map(uri => uri.fsPath).map(this.mapPathsToFiles()));
await this.executeWithProgress(
(cancelToken: vscode.CancellationTokenSource) => this.writeFiles(files, folderNode, cancelToken),
localize('uploading', 'Uploading files to HDFS'), true,
@@ -99,9 +99,9 @@ export class UploadFilesCommand extends ProgressCommand {
}
}
private mapPathsToFiles(): (value: string, index: number, array: string[]) => File {
return (path: string) => {
let isDir = fs.lstatSync(path).isDirectory();
private mapPathsToFiles(): (value: string, index: number, array: string[]) => Promise<File> {
return async (path: string) => {
let isDir = (await fs.lstat(path)).isDirectory();
return new File(path, isDir);
};
}
@@ -115,9 +115,9 @@ export class UploadFilesCommand extends ProgressCommand {
if (file.isDirectory) {
let dirName = fspath.basename(file.path);
let subFolder = await folderNode.mkdir(dirName);
let children: IFile[] = fs.readdirSync(file.path)
let children: IFile[] = await Promise.all((await fs.readdir(file.path))
.map(childFileName => joinHdfsPath(file.path, childFileName))
.map(this.mapPathsToFiles());
.map(this.mapPathsToFiles()));
this.writeFiles(children, subFolder, cancelToken);
} else {
await folderNode.writeFile(file);
@@ -258,7 +258,7 @@ export class SaveFileCommand extends ProgressCommand {
try {
let fileNode = await getNode<FileNode>(context, this.appContext);
if (fileNode) {
let defaultUri = getSaveableUri(this.apiWrapper, fspath.basename(fileNode.hdfsPath));
let defaultUri = await getSaveableUri(this.apiWrapper, fspath.basename(fileNode.hdfsPath));
let fileUri: vscode.Uri = await this.apiWrapper.showSaveDialog({
defaultUri: defaultUri
});
@@ -330,7 +330,7 @@ export class PreviewFileCommand extends ProgressCommand {
private async showNotebookDocument(fileName: string, connectionProfile?: azdata.IConnectionProfile,
initialContent?: string
): Promise<azdata.nb.NotebookEditor> {
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true)
let docUri: vscode.Uri = (await getSaveableUri(this.apiWrapper, fileName, true))
.with({ scheme: constants.UNTITLED_SCHEMA });
return await azdata.nb.showNotebookDocument(docUri, {
connectionProfile: connectionProfile,
@@ -340,7 +340,7 @@ export class PreviewFileCommand extends ProgressCommand {
}
private async openTextDocument(fileName: string): Promise<vscode.TextDocument> {
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true);
let docUri: vscode.Uri = await getSaveableUri(this.apiWrapper, fileName, true);
if (docUri) {
docUri = docUri.with({ scheme: constants.UNTITLED_SCHEMA });
return await this.apiWrapper.openTextDocument(docUri);

View File

@@ -8,7 +8,6 @@
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as fspath from 'path';
import * as fs from 'fs';
import * as vscode from 'vscode';
import * as utils from '../../../utils';
import * as LocalizedConstants from '../../../localizedConstants';
@@ -223,7 +222,7 @@ export class SparkConfigurationTab {
// 1. For local file Source check whether they existed.
if (this._dataModel.isMainSourceFromLocal) {
if (!fs.existsSync(this._dataModel.localFileSourcePath)) {
if (!(await utils.exists(this._dataModel.localFileSourcePath))) {
this._dataModel.showDialogError(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(this._dataModel.localFileSourcePath));
return false;
}

View File

@@ -8,7 +8,6 @@
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import * as fs from 'fs';
import * as fspath from 'path';
import * as os from 'os';
@@ -143,7 +142,7 @@ export class SparkJobSubmissionModel {
return Promise.reject(localize('sparkJobSubmission_localFileOrFolderNotSpecified.', 'Property localFilePath or hdfsFolderPath is not specified. '));
}
if (!fs.existsSync(localFilePath)) {
if (!(await utils.exists(localFilePath))) {
return Promise.reject(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(localFilePath));
}

View File

@@ -45,12 +45,12 @@ export function getTemplatePath(extensionPath: string, templateName: string): st
}
export function shellWhichResolving(cmd: string): Promise<string> {
return new Promise<string>(resolve => {
which(cmd, (err, foundPath) => {
which(cmd, async (err, foundPath) => {
if (err) {
resolve(undefined);
} else {
// NOTE: Using realpath b/c some system installs are symlinked from */bin
resolve(fs.realpathSync(foundPath));
resolve(await fs.promises.realpath(foundPath));
}
});
});

View File

@@ -20,8 +20,7 @@ import { AppContext } from './appContext';
import { DacFxService } from './dacfx/dacFxService';
import { CmsService } from './cms/cmsService';
import { CompletionExtensionParams, CompletionExtLoadRequest } from './contracts';
import { promisify } from 'util';
import { readFile } from 'fs';
import { promises as fs } from 'fs';
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
@@ -70,7 +69,7 @@ export class SqlToolsServer {
}
private async download(context: AppContext): Promise<string> {
const rawConfig = await promisify(readFile)(path.join(context.extensionContext.extensionPath, 'config.json'));
const rawConfig = await fs.readFile(path.join(context.extensionContext.extensionPath, 'config.json'));
this.config = JSON.parse(rawConfig.toString());
this.config.installDirectory = path.join(__dirname, this.config.installDirectory);
this.config.proxy = vscode.workspace.getConfiguration('http').get('proxy');

View File

@@ -10,8 +10,7 @@ import * as crypto from 'crypto';
import * as os from 'os';
import * as findRemoveSync from 'find-remove';
import * as constants from './constants';
import * as fs from 'fs';
import { promisify } from 'util';
import { promises as fs } from 'fs';
const configTracingLevel = 'tracingLevel';
const configLogRetentionMinutes = 'logRetentionMinutes';
@@ -31,17 +30,6 @@ export function getAppDataPath() {
}
}
export namespace pfs {
export function exists(path: string): Promise<boolean> {
return promisify(fs.exists)(path);
}
export function mkdir(path: string, mode?: number): Promise<void> {
return promisify(fs.mkdir)(path, mode);
}
}
/**
* Get a file name that is not already used in the target directory
* @param filePath source notebook file name
@@ -61,14 +49,6 @@ export function findNextUntitledEditorName(filePath: string): string {
return title;
}
export function fileExists(file: string): boolean {
return fs.existsSync(file);
}
export function copyFile(source: string, target: string): void {
fs.copyFileSync(source, target);
}
export function removeOldLogFiles(logPath: string, prefix: string): JSON {
return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
}
@@ -303,3 +283,12 @@ export function logDebug(msg: any): void {
console.log(outputMsg);
}
}
export async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -78,6 +78,7 @@ export class BookTreeItem extends vscode.TreeItem {
if (this.book.tableOfContents.sections[i].url) {
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// tslint:disable-next-line:no-sync
if (fs.existsSync(pathToNotebook)) {
this._previousUri = pathToNotebook;
return;
@@ -93,6 +94,7 @@ export class BookTreeItem extends vscode.TreeItem {
if (this.book.tableOfContents.sections[i].url) {
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
// tslint:disable-next-line:no-sync
if (fs.existsSync(pathToNotebook)) {
this._nextUri = pathToNotebook;
return;

View File

@@ -6,18 +6,16 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as yaml from 'js-yaml';
import * as glob from 'fast-glob';
import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
import { maxBookSearchDepth, notebookConfigKey } from '../common/constants';
import { isEditorTitleFree } from '../common/utils';
import { isEditorTitleFree, exists } from '../common/utils';
import * as nls from 'vscode-nls';
import { promisify } from 'util';
import { IJupyterBookToc, IJupyterBookSection } from '../contracts/content';
const localize = nls.loadMessageBundle();
const existsAsync = promisify(fs.exists);
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem>, azdata.nb.NavigationProvider {
readonly providerId: string = 'BookNavigator';
@@ -84,7 +82,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
const bookViewer = vscode.window.createTreeView('bookTreeView', { showCollapseAll: true, treeDataProvider: this });
await vscode.commands.executeCommand('workbench.books.action.focusBooksExplorer');
this._openAsUntitled = openAsUntitled;
let books = this.getBooks();
let books = await this.getBooks();
if (books && books.length > 0) {
const rootTreeItem = books[0];
const sectionToOpen = rootTreeItem.findChildSection(urlToOpen);
@@ -92,8 +90,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
const urlPath = sectionToOpen ? sectionToOpen.url : rootTreeItem.tableOfContents.sections[0].url;
const sectionToOpenMarkdown: string = path.join(bookPath, 'content', urlPath.concat('.md'));
const sectionToOpenNotebook: string = path.join(bookPath, 'content', urlPath.concat('.ipynb'));
const markdownExists = await existsAsync(sectionToOpenMarkdown);
const notebookExists = await existsAsync(sectionToOpenNotebook);
const markdownExists = await exists(sectionToOpenMarkdown);
const notebookExists = await exists(sectionToOpenNotebook);
if (markdownExists) {
vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(sectionToOpenMarkdown));
}
@@ -208,13 +206,13 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return array.reduce((acc, val) => Array.isArray(val.sections) ? acc.concat(val).concat(this.parseJupyterSection(val.sections)) : acc.concat(val), []);
}
public getBooks(): BookTreeItem[] {
public async getBooks(): Promise<BookTreeItem[]> {
let books: BookTreeItem[] = [];
for (let i in this._tableOfContentPaths) {
let root = path.dirname(path.dirname(this._tableOfContentPaths[i]));
for (const contentPath of this._tableOfContentPaths) {
let root = path.dirname(path.dirname(contentPath));
try {
const config = yaml.safeLoad(fs.readFileSync(path.join(root, '_config.yml'), 'utf-8'));
const tableOfContents = yaml.safeLoad(fs.readFileSync(this._tableOfContentPaths[i], 'utf-8'));
const config = yaml.safeLoad((await fs.readFile(path.join(root, '_config.yml'), 'utf-8')).toString());
const tableOfContents = yaml.safeLoad((await fs.readFile(contentPath, 'utf-8')).toString());
try {
let book = new BookTreeItem({
title: config.title,
@@ -242,7 +240,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return books;
}
public getSections(tableOfContents: IJupyterBookToc, sections: IJupyterBookSection[], root: string): BookTreeItem[] {
public async getSections(tableOfContents: IJupyterBookToc, sections: IJupyterBookSection[], root: string): Promise<BookTreeItem[]> {
let notebooks: BookTreeItem[] = [];
for (let i = 0; i < sections.length; i++) {
if (sections[i].url) {
@@ -267,7 +265,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
let pathToMarkdown = path.join(root, 'content', sections[i].url.concat('.md'));
// Note: Currently, if there is an ipynb and a md file with the same name, Jupyter Books only shows the notebook.
// Following Jupyter Books behavior for now
if (fs.existsSync(pathToNotebook)) {
if (await exists(pathToNotebook)) {
let notebook = new BookTreeItem({
title: sections[i].title,
root: root,
@@ -286,7 +284,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
if (this._openAsUntitled) {
this._allNotebooks.set(path.basename(pathToNotebook), notebook);
}
} else if (fs.existsSync(pathToMarkdown)) {
} else if (await exists(pathToMarkdown)) {
let markdown = new BookTreeItem({
title: sections[i].title,
root: root,

View File

@@ -188,3 +188,12 @@ export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
port: undefined
};
}
export async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as utils from '../common/utils';
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
@@ -220,7 +220,7 @@ export class ConfigurePythonDialog {
if (useExistingPython) {
let exePath = JupyterServerInstallation.getPythonExePath(pythonLocation, true);
let pythonExists = fs.existsSync(exePath);
let pythonExists = await utils.exists(exePath);
if (!pythonExists) {
this.showErrorMessage(this.PythonNotFoundMsg);
return false;
@@ -243,27 +243,23 @@ export class ConfigurePythonDialog {
return true;
}
private isFileValid(pythonLocation: string): Promise<boolean> {
private async isFileValid(pythonLocation: string): Promise<boolean> {
let self = this;
return new Promise<boolean>(function (resolve) {
fs.stat(pythonLocation, function (err, stats) {
if (err) {
// Ignore error if folder doesn't exist, since it will be
// created during installation
if (err.code !== 'ENOENT') {
self.showErrorMessage(err.message);
resolve(false);
}
}
else {
if (stats.isFile()) {
self.showErrorMessage(self.InvalidLocationMsg);
resolve(false);
}
}
resolve(true);
});
});
try {
const stats = await fs.stat(pythonLocation);
if (stats.isFile()) {
self.showErrorMessage(self.InvalidLocationMsg);
return false;
}
} catch (err) {
// Ignore error if folder doesn't exist, since it will be
// created during installation
if (err.code !== 'ENOENT') {
self.showErrorMessage(err.message);
return false;
}
}
return true;
}
private async handleBrowse(): Promise<void> {
@@ -304,4 +300,4 @@ export class ConfigurePythonDialog {
level: azdata.window.MessageLevel.Error
};
}
}
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as glob from 'glob';
import * as utils from '../common/utils';
@@ -92,7 +91,7 @@ export class PythonPathLookup {
const cmd = `"${options.command}" ${args.join(' ')}`;
let output = await utils.executeBufferedCommand(cmd, {});
let value = output ? output.trim() : '';
if (value.length > 0 && fs.existsSync(value)) {
if (value.length > 0 && await utils.exists(value)) {
return value;
}
} catch (err) {
@@ -163,4 +162,4 @@ export class PythonPathLookup {
}
return undefined;
}
}
}

View File

@@ -67,7 +67,7 @@ export class JupyterServerInstallation {
}
private async installDependencies(backgroundOperation: azdata.BackgroundOperation): Promise<void> {
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) {
if (!(await utils.exists(this._pythonExecutable)) || this._forceInstall || this._usingExistingPython) {
window.showInformationMessage(msgInstallPkgStart);
this.outputChannel.show(true);
@@ -180,11 +180,11 @@ export class JupyterServerInstallation {
});
downloadRequest.pipe(fs.createWriteStream(pythonPackagePathLocal))
.on('close', () => {
.on('close', async () => {
//unpack python zip/tar file
this.outputChannel.appendLine(msgPythonUnpackPending);
let pythonSourcePath = path.join(installPath, constants.pythonBundleVersion);
if (!this._usingExistingPython && fs.existsSync(pythonSourcePath)) {
if (!this._usingExistingPython && await utils.exists(pythonSourcePath)) {
try {
fs.removeSync(pythonSourcePath);
} catch (err) {
@@ -256,7 +256,7 @@ export class JupyterServerInstallation {
}
}
if (fs.existsSync(this._pythonExecutable)) {
if (await utils.exists(this._pythonExecutable)) {
let pythonUserDir = await this.getPythonUserDir(this._pythonExecutable);
if (pythonUserDir) {
this.pythonEnvVarPath = pythonUserDir + delimiter + this.pythonEnvVarPath;
@@ -330,7 +330,7 @@ export class JupyterServerInstallation {
await this.configurePackagePaths();
};
let installReady = new Deferred<void>();
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) {
if (!(await utils.exists(this._pythonExecutable)) || this._forceInstall || this._usingExistingPython) {
this.apiWrapper.startBackgroundOperation({
displayName: msgTaskName,
description: msgTaskName,
@@ -522,6 +522,7 @@ export class JupyterServerInstallation {
}
let condaExePath = this.getCondaExePath();
// tslint:disable-next-line:no-sync
return fs.existsSync(condaExePath);
}
@@ -538,6 +539,7 @@ export class JupyterServerInstallation {
let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper);
let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting, useExistingInstall);
// tslint:disable-next-line:no-sync
return fs.existsSync(pythonExe);
}
@@ -568,6 +570,7 @@ export class JupyterServerInstallation {
let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey);
if (notebookConfig) {
let configPythonPath = notebookConfig[constants.pythonPathConfigKey];
// tslint:disable-next-line:no-sync
if (configPythonPath && fs.existsSync(configPythonPath)) {
path = configPythonPath;
}

View File

@@ -62,8 +62,13 @@ export class ServerInstanceUtils {
public copy(src: string, dest: string): Promise<void> {
return fs.copy(src, dest);
}
public existsSync(dirPath: string): boolean {
return fs.existsSync(dirPath);
public async exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
public generateUuid(): string {
return UUID.generateUuid();
@@ -204,7 +209,7 @@ export class PerNotebookServerInstance implements IServerInstance {
private async copyKernelsToSystemJupyterDirs(): Promise<void> {
let kernelsExtensionSource = path.join(this.options.install.extensionPath, 'kernels');
this._systemJupyterDir = path.join(this.getSystemJupyterHomeDir(), 'kernels');
if (!this.utils.existsSync(this._systemJupyterDir)) {
if (!(await this.utils.exists(this._systemJupyterDir))) {
await this.utils.mkDir(this._systemJupyterDir, this.options.install.outputChannel);
}
await this.utils.copy(kernelsExtensionSource, this._systemJupyterDir);

View File

@@ -283,9 +283,9 @@ describe.skip('BookTreeViewProviderTests', function() {
await Promise.race([tocRead, errorCase.then(() => { throw new Error('Table of Contents were not ready in time'); })]);
});
it('should show error if notebook or markdown file is missing', function(): void {
it('should show error if notebook or markdown file is missing', async function(): Promise<void> {
let books = bookTreeViewProvider.getBooks();
let children = bookTreeViewProvider.getSections({ sections: [] }, books[0].sections, rootFolderPath);
let children = await bookTreeViewProvider.getSections({ sections: [] }, (await books)[0].sections, rootFolderPath);
should(bookTreeViewProvider.errorMessage).equal('Missing file : Notebook1');
// Rest of book should be detected correctly even with a missing file
equalBookItems(children[0], expectedNotebook2);

View File

@@ -57,7 +57,7 @@ describe('Jupyter server instance', function (): void {
// Given a server instance
mockUtils.setup(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
mockUtils.setup(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve());
mockUtils.setup(u => u.existsSync(TypeMoq.It.isAnyString())).returns(() => false);
mockUtils.setup(u => u.exists(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(false));
// When I run configure
await serverInstance.configure();
@@ -65,7 +65,7 @@ describe('Jupyter server instance', function (): void {
// Then I expect a folder to have been created with config and data subdirs
mockUtils.verify(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(5));
mockUtils.verify(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3));
mockUtils.verify(u => u.existsSync(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(1));
mockUtils.verify(u => u.exists(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(1));
});
it('Should have URI info after start', async function (): Promise<void> {

View File

@@ -26,10 +26,12 @@ export class PlatformService implements IPlatformService {
}
copyFile(source: string, target: string): void {
// tslint:disable-next-line:no-sync
fs.copyFileSync(source, target);
}
fileExists(file: string): boolean {
// tslint:disable-next-line:no-sync
return fs.existsSync(file);
}
@@ -44,4 +46,4 @@ export class PlatformService implements IPlatformService {
isNotebookNameUsed(title: string): boolean {
return (azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1);
}
}
}

View File

@@ -6,7 +6,7 @@
import * as azdata from 'azdata';
import * as cp from 'child_process';
import * as fs from 'fs';
import { createWriteStream, promises as fs } from 'fs';
import * as https from 'https';
import * as os from 'os';
import * as path from 'path';
@@ -226,7 +226,7 @@ export class ResourceTypeService implements IResourceTypeService {
private download(url: string): Promise<string> {
const self = this;
const promise = new Promise<string>((resolve, reject) => {
https.get(url, function (response) {
https.get(url, async function (response) {
console.log('Download installer from: ' + url);
if (response.statusCode === 301 || response.statusCode === 302) {
// Redirect and download from new location
@@ -247,19 +247,19 @@ export class ResourceTypeService implements IResourceTypeService {
let fileName = originalFileName;
const downloadFolder = os.homedir();
let cnt = 1;
while (fs.existsSync(path.join(downloadFolder, fileName + extension))) {
while (await exists(path.join(downloadFolder, fileName + extension))) {
fileName = `${originalFileName}-${cnt}`;
cnt++;
}
fileName = path.join(downloadFolder, fileName + extension);
const file = fs.createWriteStream(fileName);
const file = createWriteStream(fileName);
response.pipe(file);
file.on('finish', () => {
file.close();
resolve(fileName);
});
file.on('error', (err) => {
fs.unlink(fileName, () => { });
file.on('error', async (err) => {
await fs.unlink(fileName);
reject(err.message);
});
});
@@ -268,3 +268,12 @@ export class ResourceTypeService implements IResourceTypeService {
}
}
async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -64,7 +64,7 @@
},
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/node": "^10.14.8",
"@types/node": "^10.14.8",
"mocha": "^5.2.0",
"should": "^13.2.1",
"typemoq": "^2.1.0",

View File

@@ -9,8 +9,7 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as os from 'os';
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
import { isNullOrUndefined } from 'util';
import { existsSync } from 'fs';
import { promises as fs } from 'fs';
import { Telemetry } from '../telemetry';
import { getEndpointName } from '../utils';
import * as mssql from '../../../mssql';
@@ -35,6 +34,15 @@ const YesButtonText: string = localize('schemaCompareDialog.Yes', 'Yes');
const NoButtonText: string = localize('schemaCompareDialog.No', 'No');
const titleFontSize: number = 13;
async function exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
export class SchemaCompareDialog {
public dialog: azdata.window.Dialog;
public dialogName: string;
@@ -194,8 +202,8 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.sourceTextBox', "Source file")
}).component();
this.sourceTextBox.onTextChanged((e) => {
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.sourceTextBox.onTextChanged(async (e) => {
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
this.targetTextBox = view.modelBuilder.inputBox().withProperties({
@@ -204,8 +212,8 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.targetTextBox', "Target file")
}).component();
this.targetTextBox.onTextChanged(() => {
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.targetTextBox.onTextChanged(async () => {
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
this.sourceServerComponent = await this.createSourceServerDropdown(view);
@@ -307,7 +315,7 @@ export class SchemaCompareDialog {
currentButton.onDidClick(async (click) => {
// file browser should open where the current dacpac is or the appropriate default folder
let rootPath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].name : os.homedir();
let defaultUri = endpoint && endpoint.packageFilePath && existsSync(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath;
let defaultUri = endpoint && endpoint.packageFilePath && await exists(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath;
let fileUris = await vscode.window.showOpenDialog(
{
@@ -351,17 +359,17 @@ export class SchemaCompareDialog {
}).component();
// show dacpac file browser
dacpacRadioButton.onDidClick(() => {
dacpacRadioButton.onDidClick(async () => {
this.sourceIsDacpac = true;
this.formBuilder.removeFormItem(this.sourceNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.sourceServerComponent);
this.formBuilder.removeFormItem(this.sourceDatabaseComponent);
this.formBuilder.insertFormItem(this.sourceDacpacComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// show server and db dropdowns or 'No active connections' text
databaseRadioButton.onDidClick(() => {
databaseRadioButton.onDidClick(async () => {
this.sourceIsDacpac = false;
if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) {
this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
@@ -370,7 +378,7 @@ export class SchemaCompareDialog {
this.formBuilder.insertFormItem(this.sourceNoActiveConnectionsText, 2, { horizontal: true, titleFontSize: titleFontSize });
}
this.formBuilder.removeFormItem(this.sourceDacpacComponent);
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// if source is currently a db, show it in the server and db dropdowns
@@ -408,17 +416,17 @@ export class SchemaCompareDialog {
}).component();
// show dacpac file browser
dacpacRadioButton.onDidClick(() => {
dacpacRadioButton.onDidClick(async () => {
this.targetIsDacpac = true;
this.formBuilder.removeFormItem(this.targetNoActiveConnectionsText);
this.formBuilder.removeFormItem(this.targetServerComponent);
this.formBuilder.removeFormItem(this.targetDatabaseComponent);
this.formBuilder.addFormItem(this.targetDacpacComponent, { horizontal: true, titleFontSize: titleFontSize });
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// show server and db dropdowns or 'No active connections' text
databaseRadioButton.onDidClick(() => {
databaseRadioButton.onDidClick(async () => {
this.targetIsDacpac = false;
this.formBuilder.removeFormItem(this.targetDacpacComponent);
if ((this.targetServerDropdown.value as ConnectionDropdownValue)) {
@@ -427,7 +435,7 @@ export class SchemaCompareDialog {
} else {
this.formBuilder.addFormItem(this.targetNoActiveConnectionsText, { horizontal: true, titleFontSize: titleFontSize });
}
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
// if target is currently a db, show it in the server and db dropdowns
@@ -450,18 +458,18 @@ export class SchemaCompareDialog {
};
}
private shouldEnableOkayButton(): boolean {
private async shouldEnableOkayButton(): Promise<boolean> {
let sourcefilled = (this.sourceIsDacpac && this.existsDacpac(this.sourceTextBox.value))
let sourcefilled = (this.sourceIsDacpac && await this.existsDacpac(this.sourceTextBox.value))
|| (!this.sourceIsDacpac && !isNullOrUndefined(this.sourceDatabaseDropdown.value) && this.sourceDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.sourceDbEditable)) !== -1);
let targetfilled = (this.targetIsDacpac && this.existsDacpac(this.targetTextBox.value))
let targetfilled = (this.targetIsDacpac && await this.existsDacpac(this.targetTextBox.value))
|| (!this.targetIsDacpac && !isNullOrUndefined(this.targetDatabaseDropdown.value) && this.targetDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.targetDbEditable)) !== -1);
return sourcefilled && targetfilled;
}
private existsDacpac(filename: string): boolean {
return !isNullOrUndefined(filename) && existsSync(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac'));
private async existsDacpac(filename: string): Promise<boolean> {
return !isNullOrUndefined(filename) && await exists(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac'));
}
protected async createSourceServerDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> {
@@ -596,9 +604,9 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.sourceDatabaseDropdown', "Source Database")
}
).component();
this.sourceDatabaseDropdown.onValueChanged((value) => {
this.sourceDatabaseDropdown.onValueChanged(async (value) => {
this.sourceDbEditable = value;
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
return {
@@ -615,9 +623,9 @@ export class SchemaCompareDialog {
ariaLabel: localize('schemaCompareDialog.targetDatabaseDropdown', "Target Database")
}
).component();
this.targetDatabaseDropdown.onValueChanged((value) => {
this.targetDatabaseDropdown.onValueChanged(async (value) => {
this.targetDbEditable = value;
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
});
return {
@@ -684,3 +692,7 @@ export class SchemaCompareDialog {
interface ConnectionDropdownValue extends azdata.CategoryValue {
connection: azdata.connection.ConnectionProfile;
}
function isNullOrUndefined(val: any): boolean {
return val === null || val === undefined;
}

View File

@@ -229,6 +229,21 @@
"translation-remind": true,
"no-standalone-editor": true,
"no-nls-in-standalone-editor": true,
"no-sync": [
true,
{
"exclude": [
"**/vs/**", // assume they are doing the right thing
"**/extensions/git/**", // assume they are doing the right thing,
"**/extensions/extension-editing/**", // assume they are doing the right thing,
"**/json-language-features/**", // assume they are doing the right thing,
"**/vscode-test-resolver/**", // testing doesn't matter
"**/integration-tests/**", // testing doesn't matter
"**/*.test.*", // testing doesn't matter
"**/test/**" // testing doesn't matter
]
}
],
"no-useless-strict": true
},
"defaultSeverity": "warning"