From ebadc7550fb1b344ccce4b2ccad855d47c4113a3 Mon Sep 17 00:00:00 2001 From: ivanovit Date: Wed, 30 May 2018 16:18:12 +0300 Subject: [PATCH 01/12] Remove ipc Use the debug adapter events --- package.json | 3 - src/analytics/analyticsService.ts | 4 +- src/common/extensionProtocol.ts | 14 ++++ src/debug-adapter/nativeScriptDebugAdapter.ts | 48 ++++++++---- src/ipc/extensionClient.ts | 73 ------------------- src/ipc/extensionProtocol.ts | 23 ------ src/ipc/sockedId.ts | 4 - src/main.ts | 38 +++++++--- src/project/iosProject.ts | 2 - src/services/debugAdapterServices.ts | 21 ------ src/services/extensionHostServices.ts | 14 ++-- .../iOSTeamService.ts} | 66 +---------------- 12 files changed, 86 insertions(+), 224 deletions(-) create mode 100644 src/common/extensionProtocol.ts delete mode 100644 src/ipc/extensionClient.ts delete mode 100644 src/ipc/extensionProtocol.ts delete mode 100644 src/ipc/sockedId.ts delete mode 100644 src/services/debugAdapterServices.ts rename src/{ipc/extensionServer.ts => services/iOSTeamService.ts} (58%) diff --git a/package.json b/package.json index 06664f9..a00e743 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "lodash": "^4.17.10", - "node-ipc": "8.10.3", "universal-analytics": "0.4.13", "uuid": "^3.2.1", "vscode-chrome-debug-core": "^3.23.11", @@ -36,8 +35,6 @@ "@types/lodash": "^4.14.109", "@types/mocha": "2.2.41", "@types/node": "6.0.46", - "@types/source-map": "~0.1.0", - "chrome-remote-debug-protocol": "https://github.com/roblourens/chrome-remote-debug-protocol/tarball/master", "mocha": "2.5.3", "typescript": "2.6.2", "vsce": "~1.36.0", diff --git a/src/analytics/analyticsService.ts b/src/analytics/analyticsService.ts index b488e36..729d7a1 100644 --- a/src/analytics/analyticsService.ts +++ b/src/analytics/analyticsService.ts @@ -100,12 +100,12 @@ export class AnalyticsService { this._globalState.update(AnalyticsService.HAS_ANALYTICS_PROMPT_SHOWN_KEY, true); - Services.workspaceConfigService().isAnalyticsEnabled = shouldEnableAnalytics; + Services.workspaceConfigService.isAnalyticsEnabled = shouldEnableAnalytics; this.updateAnalyticsEnabled(); } private updateAnalyticsEnabled() { - this._analyticsEnabled = Services.workspaceConfigService().isAnalyticsEnabled; + this._analyticsEnabled = Services.workspaceConfigService.isAnalyticsEnabled; if(this._analyticsEnabled && !this._gua) { this._gua = new GUAService('UA-111455-29', this._baseInfo); diff --git a/src/common/extensionProtocol.ts b/src/common/extensionProtocol.ts new file mode 100644 index 0000000..0c59f26 --- /dev/null +++ b/src/common/extensionProtocol.ts @@ -0,0 +1,14 @@ +export interface Request { + id: string; + service: string; + method: string; + args: any[]; +} + +export interface Response { + requestId: string; + result: Object; +} + +export const BEFORE_DEBUG_START = "before-debug-start"; +export const NS_DEBUG_ADAPTER_MESSAGE = "ns-debug-adapter-message"; \ No newline at end of file diff --git a/src/debug-adapter/nativeScriptDebugAdapter.ts b/src/debug-adapter/nativeScriptDebugAdapter.ts index 79223bf..0e2dddb 100644 --- a/src/debug-adapter/nativeScriptDebugAdapter.ts +++ b/src/debug-adapter/nativeScriptDebugAdapter.ts @@ -1,5 +1,5 @@ -import { IInitializeRequestArgs, ChromeDebugAdapter, IAttachRequestArgs, ISetBreakpointsArgs, ISetBreakpointsResponseBody, ILaunchRequestArgs, ITelemetryPropertyCollector } from 'vscode-chrome-debug-core'; -import { OutputEvent, TerminatedEvent } from 'vscode-debugadapter'; +import { ChromeDebugAdapter } from 'vscode-chrome-debug-core'; +import { OutputEvent, TerminatedEvent, Event } from 'vscode-debugadapter'; import * as utils from '../common/utilities'; import {IosProject} from '../project/iosProject'; import {AndroidProject} from '../project/androidProject'; @@ -7,16 +7,21 @@ import { ChildProcess } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import {DebugResult} from '../project/project'; -import {Services} from '../services/debugAdapterServices' +import * as extProtocol from '../common/extensionProtocol'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import {Logger} from '../common/logger'; +import {NativeScriptCli} from '../project/nativeScriptCli'; export class NativeScriptDebugAdapter extends ChromeDebugAdapter { private _tnsProcess: ChildProcess; + private _idCounter = 0; + private _pendingRequests: Object= {}; public async attach(args: any): Promise { return await this.processRequestAndAttach(args); } - public async launch(args: any, telemetryPropertyCollector?: ITelemetryPropertyCollector): Promise { + public async launch(args: any): Promise { return await this.processRequestAndAttach(args); } @@ -39,19 +44,24 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { return super.attach(transformedArgs); } + public onExtensionResponse(response) { + this._pendingRequests[response.requestId](response.result); + delete this._pendingRequests[response.requestId]; + } + private async processRequest(args: any) : Promise { args = this.translateArgs(args); - Services.appRoot = args.appRoot; - Services.extensionClient().cleanBeforeDebug(); - const settings = await Services.extensionClient().getInitSettings(); - Services.cliPath = settings.tnsPath || Services.cliPath; + this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START)); + + const tnsPath = await this.callRemoteMethod('workspaceConfigService', 'tnsPath'); + const cli = new NativeScriptCli(tnsPath, new Logger()); const project = args.platform == "ios" ? - new IosProject(args.appRoot, Services.cli()) : - new AndroidProject(args.appRoot, Services.cli()); + new IosProject(args.appRoot, cli) : + new AndroidProject(args.appRoot, cli); - Services.extensionClient().analyticsLaunchDebugger({ request: args.request, platform: args.platform }); + this.callRemoteMethod('analyticsService', 'launchDebugger', args.request, args.platform); // Run CLI Command this.log(`[NSDebugAdapter] Using tns CLI v${project.cli.version.version} on path '${project.cli.path}'\n`); @@ -64,9 +74,9 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { // For iOS the TeamID is required if there's more than one. // Therefore if not set, show selection to the user. if(args.platform && args.platform.toLowerCase() === 'ios') { - let teamId = this.getTeamId(path.join(Services.appRoot, 'app'), tnsArgs); + let teamId = this.getTeamId(path.join(args.appRoot, 'app'), tnsArgs); if(!teamId) { - let selectedTeam = (await Services.extensionClient().selectTeam()); + let selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam'); if(selectedTeam) { // add the selected by the user Team Id tnsArgs = (tnsArgs || []).concat(['--teamId', selectedTeam.id]); @@ -174,5 +184,15 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { private readTeamId(appRoot): string { return this.readXCConfig(appRoot, "DEVELOPMENT_TEAM"); - } + } + + private callRemoteMethod(service: string, method: string, ...args: any[]): Promise { + let request: extProtocol.Request = { id: `req${++this._idCounter}`, service: service, method: method, args: args }; + + return new Promise((res, rej) => { + this._pendingRequests[request.id] = res; + + this._session.sendEvent(new Event(extProtocol.NS_DEBUG_ADAPTER_MESSAGE, request)); + }); + } } \ No newline at end of file diff --git a/src/ipc/extensionClient.ts b/src/ipc/extensionClient.ts deleted file mode 100644 index 377f4fc..0000000 --- a/src/ipc/extensionClient.ts +++ /dev/null @@ -1,73 +0,0 @@ -import * as extProtocol from './extensionProtocol'; -import {Services} from "../services/debugAdapterServices"; -import {getSocketId} from "./sockedId"; - -const ipc = require('node-ipc'); - -export class ExtensionClient { - private _appRoot: string; - private _idCounter = 0; - private _pendingRequests: Object; - private _socketId: string; - - private _ipcClientInitialized: Promise; - - constructor(appRoot: string) { - this._appRoot = appRoot; - this._idCounter = 0; - this._pendingRequests = {}; - - this._socketId = getSocketId(); - - ipc.config.id = 'debug-adapter-' + process.pid; - ipc.config.retry = 1500; - ipc.config.maxRetries = 5; - - this._ipcClientInitialized = new Promise((res, rej) => { - ipc.connectTo( - this._socketId, - () => { - ipc.of[this._socketId].on('connect', () => { - res(); - }); - - ipc.of[this._socketId].on('error', error => { - Services.logger().log(`[ExtensionClient] error: ${JSON.stringify(error)}\n`); - }); - - ipc.of[this._socketId].on('extension-protocol-message', (response: extProtocol.Response) => { - (<(result: Object) => void>this._pendingRequests[response.requestId])(response.result); - }); - } - ); - }); - } - - private callRemoteMethod(method: string, args?: Object): Promise { - let request: extProtocol.Request = {id: 'req' + (++this._idCounter), method: method, args: args}; - return new Promise((res, rej) => { - this._pendingRequests[request.id] = res; - ipc.of[this._socketId].emit('extension-protocol-message', request); - }); - } - - public getInitSettings(): Promise { - return >(this.callRemoteMethod('getInitSettings')); - } - - public cleanBeforeDebug(): Promise { - return this.callRemoteMethod('cleanBeforeDebug'); - } - - public analyticsLaunchDebugger(args: extProtocol.AnalyticsLaunchDebuggerArgs): Promise { - return this.callRemoteMethod('analyticsLaunchDebugger', args); - } - - public runRunCommand(args: extProtocol.AnalyticsRunRunCommandArgs): Promise { - return this.callRemoteMethod('runRunCommand', args); - } - - public selectTeam(): Promise<{ id: string, name: string }> { - return >(this.callRemoteMethod('selectTeam')); - } -} \ No newline at end of file diff --git a/src/ipc/extensionProtocol.ts b/src/ipc/extensionProtocol.ts deleted file mode 100644 index c3740eb..0000000 --- a/src/ipc/extensionProtocol.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface Request { - id: string; - method: string; - args: Object; -} - -export interface Response { - requestId: string; - result: Object; -} - -export interface AnalyticsLaunchDebuggerArgs { - request: string; - platform: string; -} - -export interface AnalyticsRunRunCommandArgs { - platform: string; -} - -export interface InitSettingsResult { - tnsPath: string; -} \ No newline at end of file diff --git a/src/ipc/sockedId.ts b/src/ipc/sockedId.ts deleted file mode 100644 index 54328e3..0000000 --- a/src/ipc/sockedId.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function getSocketId(): string { - // let's KISS for now - I doubt users will want to simultaneously debug 2 apps anyway - return 'vs-ns-ext'; -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 8f30f9e..f3a5e94 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,13 +5,14 @@ import {Project} from './project/project'; import {IosProject} from './project/iosProject'; import {AndroidProject} from './project/androidProject'; import * as utils from './common/utilities'; +import * as extProtocol from './common/extensionProtocol'; // this method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { Services.globalState = context.globalState; - Services.cliPath = Services.workspaceConfigService().tnsPath || Services.cliPath; - Services.extensionServer().start(); - Services.analyticsService().initialize(); + Services.cliPath = Services.workspaceConfigService.tnsPath || Services.cliPath; + + Services.analyticsService.initialize(); // Check if NativeScript CLI is installed globally and if it is compatible with the extension version let cliVersion = Services.cli().version; @@ -24,6 +25,7 @@ export function activate(context: vscode.ExtensionContext) { channel.show(); }); + let beforeBuildDisposables = new Array(); let runCommand = (project: Project) => { if (vscode.workspace.rootPath === undefined) { vscode.window.showErrorMessage('No workspace opened.'); @@ -35,7 +37,7 @@ export function activate(context: vscode.ExtensionContext) { runChannel.clear(); runChannel.show(vscode.ViewColumn.Two); - Services.analyticsService().runRunCommand(project.platformName()); + Services.analyticsService.runRunCommand(project.platformName()); let tnsProcess = project.run(); tnsProcess.on('error', err => { @@ -60,8 +62,7 @@ export function activate(context: vscode.ExtensionContext) { }; context.subscriptions.push(disposable); - - Services.extensionServer().registerForCleanBeforeDebug(disposable); + beforeBuildDisposables.push(disposable); }; let runIosCommand = vscode.commands.registerCommand('nativescript.runIos', () => { @@ -72,6 +73,27 @@ export function activate(context: vscode.ExtensionContext) { return runCommand(new AndroidProject(vscode.workspace.rootPath, Services.cli())); }); + context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent(event => { + if(event.event === extProtocol.BEFORE_DEBUG_START) { + beforeBuildDisposables.forEach(disposable => disposable.dispose()); + } + + if(event.event === extProtocol.NS_DEBUG_ADAPTER_MESSAGE) { + const request = event.body as extProtocol.Request; + const service = Services[request.service]; + const method = service[request.method]; + const response = typeof method === 'function' ? service[request.method].call(service, ...request.args) : method; + + if(response.then) { + response.then(actualResponse => event.session.customRequest("onExtensionResponse", { requestId: request.id, result: actualResponse })); + + return; + } + + event.session.customRequest("onExtensionResponse", { requestId: request.id, result: response }) + } + })); + context.subscriptions.push(runIosCommand); context.subscriptions.push(runAndroidCommand); context.subscriptions.push(showOutputChannelCommand); @@ -87,8 +109,4 @@ function createInfoChannel(cliVersion: string): vscode.OutputChannel { channel.appendLine(`NativeScript CLI: ${cliVersion}`); return channel; -} - -export function deactivate() { - Services.extensionServer().stop(); } \ No newline at end of file diff --git a/src/project/iosProject.ts b/src/project/iosProject.ts index 5effe6d..3f2aad4 100644 --- a/src/project/iosProject.ts +++ b/src/project/iosProject.ts @@ -5,8 +5,6 @@ import {Project, DebugResult} from './project'; import * as scanner from './streamScanner'; import {Version} from '../common/version'; import {NativeScriptCli} from './nativeScriptCli'; -import {Services} from '../services/debugAdapterServices'; -import {Tags} from '../common/logger'; export class IosProject extends Project { diff --git a/src/services/debugAdapterServices.ts b/src/services/debugAdapterServices.ts deleted file mode 100644 index 258dd83..0000000 --- a/src/services/debugAdapterServices.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {Services as BaseServices} from './services'; -import {ExtensionClient} from '../ipc/extensionClient'; - -export class DebugAdapterServices extends BaseServices { - private _extensionClient: ExtensionClient; - private _appRoot: string; - - public get appRoot(): string { return this._appRoot; } - - public set appRoot(appRoot: string) { this._appRoot = appRoot; } - - public extensionClient(): ExtensionClient { - if (!this._extensionClient && !this._appRoot) { - throw new Error("appRoot has no value."); - } - this._extensionClient = this._extensionClient || new ExtensionClient(this._appRoot); - return this._extensionClient; - } -} - -export let Services = new DebugAdapterServices(); \ No newline at end of file diff --git a/src/services/extensionHostServices.ts b/src/services/extensionHostServices.ts index 855c5b2..d1cf061 100644 --- a/src/services/extensionHostServices.ts +++ b/src/services/extensionHostServices.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import {Services as BaseServices} from './services'; -import {ExtensionServer} from '../ipc/extensionServer'; +import {iOSTeamService} from './iOSTeamService'; import {AnalyticsService} from '../analytics/analyticsService'; import {WorkspaceConfigService} from '../common/workspaceConfigService'; @@ -8,24 +8,24 @@ export class ExtensionHostServices extends BaseServices { private _globalState: vscode.Memento; private _workspaceConfigService: WorkspaceConfigService; - private _extensionServer: ExtensionServer; + private _iOSTeamService: iOSTeamService; private _analyticsService: AnalyticsService; public get globalState(): vscode.Memento { return this._globalState; } public set globalState(globalState: vscode.Memento) { this._globalState = globalState; } - public workspaceConfigService(): WorkspaceConfigService { + public get workspaceConfigService(): WorkspaceConfigService { this._workspaceConfigService = this._workspaceConfigService || new WorkspaceConfigService(); return this._workspaceConfigService; } - public extensionServer(): ExtensionServer { - this._extensionServer = this._extensionServer || new ExtensionServer(); - return this._extensionServer; + public get iOSTeamService(): iOSTeamService { + this._iOSTeamService = this._iOSTeamService || new iOSTeamService(); + return this._iOSTeamService; } - public analyticsService(): AnalyticsService { + public get analyticsService(): AnalyticsService { this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState); return this._analyticsService; } diff --git a/src/ipc/extensionServer.ts b/src/services/iOSTeamService.ts similarity index 58% rename from src/ipc/extensionServer.ts rename to src/services/iOSTeamService.ts index bc9b247..57454ba 100644 --- a/src/ipc/extensionServer.ts +++ b/src/services/iOSTeamService.ts @@ -2,72 +2,8 @@ import * as path from 'path'; import * as fs from 'fs'; import * as vscode from 'vscode'; import {QuickPickItem} from 'vscode'; -import * as extProtocol from './extensionProtocol'; -import {Services} from '../services/extensionHostServices'; -import {getSocketId} from "./sockedId"; -import {Disposable} from "vscode"; - -let ipc = require('node-ipc'); - -export class ExtensionServer { - private _isRunning: boolean; - private disposablesBeforeDebug: Disposable[] = new Array(); - - constructor() { - this._isRunning = false; - } - - public start() { - if (!this._isRunning) { - ipc.config.id = getSocketId(); - ipc.serve( - () => { - ipc.server.on('extension-protocol-message', (data: extProtocol.Request, socket) => { - return (>this[data.method].call(this, data.args)).then(result => { - let response: extProtocol.Response = {requestId: data.id, result: result}; - return ipc.server.emit(socket, 'extension-protocol-message', response); - }); - }); - }); - ipc.server.start(); - this._isRunning = true; - } - return this._isRunning; - } - - public stop() { - if (this._isRunning) { - ipc.server.stop(); - this._isRunning = false; - } - } - - public registerForCleanBeforeDebug(...disposables: Disposable[]) { - this.disposablesBeforeDebug.push(...disposables) - } - - public cleanBeforeDebug() { - this.disposablesBeforeDebug.forEach(disposable => disposable.dispose()); - return Promise.resolve(); - } - - public isRunning() { - return this._isRunning; - } - - public getInitSettings(): Promise { - let tnsPath = Services.workspaceConfigService().tnsPath; - return Promise.resolve({tnsPath: tnsPath}); - } - - public analyticsLaunchDebugger(args: extProtocol.AnalyticsLaunchDebuggerArgs): Promise { - return Services.analyticsService().launchDebugger(args.request, args.platform); - } - - public runRunCommand(args: extProtocol.AnalyticsRunRunCommandArgs): Promise { - return Services.analyticsService().runRunCommand(args.platform); - } +export class iOSTeamService { public selectTeam(): Promise<{ id: string, name: string }> { return new Promise((resolve, reject) => { const workspaceTeamId = vscode.workspace.getConfiguration().get("nativescript.iosTeamId"); From 9f1e25a7ec62d9b68e2a5200e96ea77d4057babc Mon Sep 17 00:00:00 2001 From: ivanovit Date: Thu, 31 May 2018 15:44:24 +0300 Subject: [PATCH 02/12] Use chrome-debug-adapter logger Also added extension host logger --- src/common/logger.ts | 107 ++---------------- src/debug-adapter/nativeScriptDebugAdapter.ts | 5 +- src/main.ts | 24 ++-- src/project/nativeScriptCli.ts | 12 +- src/services/channelLogger.ts | 17 +++ src/services/services.ts | 13 +-- 6 files changed, 54 insertions(+), 124 deletions(-) create mode 100644 src/services/channelLogger.ts diff --git a/src/common/logger.ts b/src/common/logger.ts index 49fde9d..733285d 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -1,102 +1,13 @@ -import * as fs from 'fs'; - -export enum LoggerMessageType { - Log, - Info, - Warning, - Error +enum LogLevel { + Verbose = 0, + Log = 1, + Warn = 2, + Error = 3, + Stop = 4, } -export interface LoggerMessageEventArgs { - message: string, - type: LoggerMessageType +interface ILogger { + log(msg: string, level?: LogLevel): void; } -export type LoggerHandler = ((args: LoggerMessageEventArgs) => void); -type TaggedLoggerHandler = { handler: LoggerHandler, tags: string[] }; - -/** - * The logger is a singleton. - */ -export class Logger { - private _handlers: TaggedLoggerHandler[]; - - constructor() { - this._handlers = []; - } - - private handleMessage(message: string, type: LoggerMessageType = LoggerMessageType.Log, tag: string = null) { - for (let handler of this._handlers) { - if (!handler.tags || handler.tags.length == 0 || handler.tags.indexOf(tag) > -1) { - handler.handler({ message: message, type: type }); - } - } - } - - public log(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Log, tag); - } - - public info(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Info, tag); - } - - public warn(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Warning, tag); - } - - public error(message: string, tag: string = null): void { - this.handleMessage(message, LoggerMessageType.Error, tag); - } - - public addHandler(handler: LoggerHandler, tags: string[] = null) { - tags = tags || []; - this._handlers.push({ handler: handler, tags: tags }); - } - - /** - * Removes all occurrence of this handler, ignoring the associated tags - */ - public removeHandler(handlerToRemove: LoggerHandler) { - let i = this._handlers.length; - while (i--) { - if (this._handlers[i].handler == handlerToRemove) { - this._handlers.splice(i, 1); - } - } - } -} - -export namespace Tags { - export const FrontendMessage: string = "LoggerTag.FrontendMessage"; -} - -export namespace Handlers { - export function stdStreamsHandler(args: LoggerMessageEventArgs) { - var message = args.message.replace(/\n$/, ""); - switch(args.type) { - case LoggerMessageType.Log: - console.log(message); - break; - case LoggerMessageType.Info: - console.info(message); - break; - case LoggerMessageType.Warning: - console.warn(message); - break; - case LoggerMessageType.Error: - console.error(message); - break; - } - }; - - export function createStreamHandler(stream: fs.WriteStream, encoding: string = 'utf8'): LoggerHandler { - let isStreamClosed = false; - stream.on('close', () => { isStreamClosed = true; }); - return (args: LoggerMessageEventArgs) => { - if (stream && !isStreamClosed) { - stream.write(args.message, encoding); - } - } - } -} \ No newline at end of file +export { ILogger, LogLevel } \ No newline at end of file diff --git a/src/debug-adapter/nativeScriptDebugAdapter.ts b/src/debug-adapter/nativeScriptDebugAdapter.ts index 0e2dddb..30e3052 100644 --- a/src/debug-adapter/nativeScriptDebugAdapter.ts +++ b/src/debug-adapter/nativeScriptDebugAdapter.ts @@ -1,4 +1,4 @@ -import { ChromeDebugAdapter } from 'vscode-chrome-debug-core'; +import { ChromeDebugAdapter, logger } from 'vscode-chrome-debug-core'; import { OutputEvent, TerminatedEvent, Event } from 'vscode-debugadapter'; import * as utils from '../common/utilities'; import {IosProject} from '../project/iosProject'; @@ -9,7 +9,6 @@ import * as path from 'path'; import {DebugResult} from '../project/project'; import * as extProtocol from '../common/extensionProtocol'; import { DebugProtocol } from 'vscode-debugprotocol'; -import {Logger} from '../common/logger'; import {NativeScriptCli} from '../project/nativeScriptCli'; export class NativeScriptDebugAdapter extends ChromeDebugAdapter { @@ -55,7 +54,7 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START)); const tnsPath = await this.callRemoteMethod('workspaceConfigService', 'tnsPath'); - const cli = new NativeScriptCli(tnsPath, new Logger()); + const cli = new NativeScriptCli(tnsPath, logger); const project = args.platform == "ios" ? new IosProject(args.appRoot, cli) : diff --git a/src/main.ts b/src/main.ts index f3a5e94..e8fdb52 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,13 +6,17 @@ import {IosProject} from './project/iosProject'; import {AndroidProject} from './project/androidProject'; import * as utils from './common/utilities'; import * as extProtocol from './common/extensionProtocol'; +import { ChannelLogger } from './services/channelLogger'; +import { ILogger } from './common/logger'; // this method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { Services.globalState = context.globalState; Services.cliPath = Services.workspaceConfigService.tnsPath || Services.cliPath; - Services.analyticsService.initialize(); + const channel = vscode.window.createOutputChannel("NativeScript Extension"); + const logger = new ChannelLogger(channel); + Services.logger = logger; // Check if NativeScript CLI is installed globally and if it is compatible with the extension version let cliVersion = Services.cli().version; @@ -20,7 +24,10 @@ export function activate(context: vscode.ExtensionContext) { vscode.window.showErrorMessage(cliVersion.errorMessage); } - let channel = createInfoChannel(cliVersion.version.toString()); + logExtensionInfo(logger, cliVersion.version.toString()); + + Services.analyticsService.initialize(); + let showOutputChannelCommand = vscode.commands.registerCommand('nativescript.showOutputChannel', () => { channel.show(); }); @@ -99,14 +106,11 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(showOutputChannelCommand); } -function createInfoChannel(cliVersion: string): vscode.OutputChannel { - let channel = vscode.window.createOutputChannel("NativeScript Extension"); +function logExtensionInfo(logger: ILogger, cliVersion: string): void { const packageJSON = vscode.extensions.getExtension("Telerik.nativescript").packageJSON; - packageJSON.version && channel.appendLine(`Version: ${packageJSON.version}`); - packageJSON.buildVersion && channel.appendLine(`Build version: ${packageJSON.buildVersion}`); - packageJSON.commitId && channel.appendLine(`Commit id: ${packageJSON.commitId}`); - channel.appendLine(`NativeScript CLI: ${cliVersion}`); - - return channel; + packageJSON.version && logger.log(`Version: ${packageJSON.version}`); + packageJSON.buildVersion && logger.log(`Build version: ${packageJSON.buildVersion}`); + packageJSON.commitId && logger.log(`Commit id: ${packageJSON.commitId}`); + logger.log(`NativeScript CLI: ${cliVersion}`); } \ No newline at end of file diff --git a/src/project/nativeScriptCli.ts b/src/project/nativeScriptCli.ts index a7669b0..aaf75b5 100644 --- a/src/project/nativeScriptCli.ts +++ b/src/project/nativeScriptCli.ts @@ -1,6 +1,6 @@ import {spawn, execSync, ChildProcess} from 'child_process'; import {Version} from '../common/version'; -import {Logger, Tags} from '../common/logger'; +import { ILogger } from '../common/logger'; import * as utils from '../common/utilities'; import * as os from 'os'; @@ -47,9 +47,9 @@ export class NativeScriptCli { private _path: string; private _shellPath: string; private _cliVersion: CliVersion; - private _logger: Logger; + private _logger: ILogger; - constructor(cliPath: string, logger: Logger) { + constructor(cliPath: string, logger: ILogger) { this._path = cliPath; this._logger = logger; @@ -66,7 +66,7 @@ export class NativeScriptCli { versionStr = this.executeSync(["--version"], undefined); } catch(e) { - this._logger.log(e, Tags.FrontendMessage); + this._logger.log(e); throw new Error("NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting to explicitly set the absolute path to the NativeScript CLI."); } let cliVersion: Version = versionStr ? Version.parse(versionStr) : null; @@ -85,7 +85,7 @@ export class NativeScriptCli { public executeSync(args: string[], cwd: string): string { args.unshift("--analyticsClient", "VSCode"); let command: string = `${this._path} ${args.join(' ')}`; - this._logger.log(`[NativeScriptCli] execute: ${command}\n`, Tags.FrontendMessage); + this._logger.log(`[NativeScriptCli] execute: ${command}`,); return execSync(command, { encoding: "utf8", cwd: cwd, shell: this._shellPath}).toString().trim(); } @@ -93,7 +93,7 @@ export class NativeScriptCli { public execute(args: string[], cwd: string): ChildProcess { args.unshift("--analyticsClient", "VSCode"); let command: string = `${this._path} ${args.join(' ')}`; - this._logger.log(`[NativeScriptCli] execute: ${command}\n`, Tags.FrontendMessage); + this._logger.log(`[NativeScriptCli] execute: ${command}`); let options = { cwd: cwd, shell: this._shellPath }; let child: ChildProcess = spawn(this._path, args, options); diff --git a/src/services/channelLogger.ts b/src/services/channelLogger.ts new file mode 100644 index 0000000..676fd76 --- /dev/null +++ b/src/services/channelLogger.ts @@ -0,0 +1,17 @@ +import { ILogger, LogLevel } from '../common/logger'; +import { OutputChannel } from 'vscode'; + +export class ChannelLogger implements ILogger { + private minLogLevel: LogLevel = LogLevel.Log; + private channel: OutputChannel; + + constructor(channel: OutputChannel) { + this.channel = channel; + } + + log(msg: string, level: LogLevel = LogLevel.Log): void { + if (level >= this.minLogLevel) { + this.channel.appendLine(msg); + } + } +} \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 56b684e..0524451 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,23 +1,22 @@ -import {Logger} from '../common/logger'; +import {ILogger} from '../common/logger'; import {NativeScriptCli} from '../project/nativeScriptCli'; export class Services { protected _cliPath: string; - protected _logger: Logger; + protected _logger: ILogger; protected _cli: NativeScriptCli; public get cliPath(): string { return this._cliPath; } public set cliPath(cliPath: string) { this._cliPath = cliPath; } - public logger(): Logger { - this._logger = this._logger || new Logger(); - return this._logger; - } + public get logger(): ILogger { return this._logger; } + + public set logger(logger: ILogger) { this._logger = logger; } public cli(): NativeScriptCli { - this._cli = this._cli || new NativeScriptCli(this._cliPath, this.logger()); + this._cli = this._cli || new NativeScriptCli(this._cliPath, this.logger); return this._cli; } } From a42a2b812b2439db0a204850b939d6bbeeb7741e Mon Sep 17 00:00:00 2001 From: ivanovit Date: Fri, 1 Jun 2018 14:19:23 +0300 Subject: [PATCH 03/12] Use semver to compare versions --- package.json | 1 + src/analytics/analyticsService.ts | 7 +-- src/common/utilities.ts | 10 ---- src/common/version.ts | 28 --------- src/debug-adapter/nativeScriptDebugAdapter.ts | 3 +- src/main.ts | 40 ++++++++----- src/project/androidProject.ts | 1 - src/project/iosProject.ts | 1 - src/project/nativeScriptCli.ts | 59 ++----------------- src/project/project.ts | 1 - src/services/extensionHostServices.ts | 5 +- 11 files changed, 40 insertions(+), 116 deletions(-) delete mode 100644 src/common/version.ts diff --git a/package.json b/package.json index a00e743..3345370 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "lodash": "^4.17.10", + "semver": "^5.5.0", "universal-analytics": "0.4.13", "uuid": "^3.2.1", "vscode-chrome-debug-core": "^3.23.11", diff --git a/src/analytics/analyticsService.ts b/src/analytics/analyticsService.ts index 729d7a1..e1fb01b 100644 --- a/src/analytics/analyticsService.ts +++ b/src/analytics/analyticsService.ts @@ -1,5 +1,4 @@ import * as os from 'os'; -import { Version } from '../common/version'; import { GUAService } from './guaService'; import { AnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; import { Services } from '../services/extensionHostServices'; @@ -22,14 +21,14 @@ export class AnalyticsService { private _gua: GUAService; private _analyticsEnabled: boolean; - constructor(globalState: vscode.Memento) { + constructor(globalState: vscode.Memento, cliVersion: string, extensionVersion: string) { this._globalState = globalState; vscode.workspace.onDidChangeConfiguration(() => this.updateAnalyticsEnabled()); this._baseInfo = { - cliVersion: Services.cli().version.toString(), - extensionVersion: utils.getInstalledExtensionVersion().toString(), + cliVersion, + extensionVersion, operatingSystem: AnalyticsService.getOperatingSystem(), clientId: this.getOrGenerateClientId() }; diff --git a/src/common/utilities.ts b/src/common/utilities.ts index c54e049..6716215 100644 --- a/src/common/utilities.ts +++ b/src/common/utilities.ts @@ -1,16 +1,6 @@ -import {Version} from './version'; import {ChildProcess, exec} from 'child_process'; import * as os from 'os'; - -export function getInstalledExtensionVersion(): Version { - return Version.parse(require('../../package.json').version); -} - -export function getMinSupportedCliVersion(): Version { - return Version.parse(require('../../package.json').minNativescriptCliVersion); -} - export function killProcess(childProcess: ChildProcess) : void { switch (process.platform) { case "win32": diff --git a/src/common/version.ts b/src/common/version.ts deleted file mode 100644 index 59ba47d..0000000 --- a/src/common/version.ts +++ /dev/null @@ -1,28 +0,0 @@ -export class Version { - private _version: number[]; - - public static parse(versionStr: string): Version { - if (versionStr === null) { - return null; - } - let version: number[] = versionStr.split('.').map((str, index, array) => parseInt(str)); - for(let i = version.length; i < 3; i++) { - version.push(0); - } - return new Version(version); - } - - constructor(version: number[]) { - this._version = version; - } - - public toString(): string { - return `${this._version[0]}.${this._version[1]}.${this._version[2]}`; - } - - public compareBySubminorTo(other: Version): number { - let v1 = this._version; - let v2 = other._version; - return (v1[0] - v2[0] != 0) ? (v1[0] - v2[0]) : (v1[1] - v2[1] != 0) ? v1[1] - v2[1] : v1[2] - v2[2]; - } -} diff --git a/src/debug-adapter/nativeScriptDebugAdapter.ts b/src/debug-adapter/nativeScriptDebugAdapter.ts index 30e3052..9a50ab3 100644 --- a/src/debug-adapter/nativeScriptDebugAdapter.ts +++ b/src/debug-adapter/nativeScriptDebugAdapter.ts @@ -63,7 +63,8 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { this.callRemoteMethod('analyticsService', 'launchDebugger', args.request, args.platform); // Run CLI Command - this.log(`[NSDebugAdapter] Using tns CLI v${project.cli.version.version} on path '${project.cli.path}'\n`); + const version = project.cli.executeGetVersion(); + this.log(`[NSDebugAdapter] Using tns CLI v${version} on path '${project.cli.path}'\n`); this.log('[NSDebugAdapter] Running tns command...\n'); let cliCommand: DebugResult; diff --git a/src/main.ts b/src/main.ts index e8fdb52..15f6346 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,4 @@ import * as vscode from 'vscode'; -import {CliVersion} from './project/nativeScriptCli'; import {Services} from './services/extensionHostServices'; import {Project} from './project/project'; import {IosProject} from './project/iosProject'; @@ -8,6 +7,7 @@ import * as utils from './common/utilities'; import * as extProtocol from './common/extensionProtocol'; import { ChannelLogger } from './services/channelLogger'; import { ILogger } from './common/logger'; +import * as semver from "semver"; // this method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { @@ -15,16 +15,28 @@ export function activate(context: vscode.ExtensionContext) { Services.cliPath = Services.workspaceConfigService.tnsPath || Services.cliPath; const channel = vscode.window.createOutputChannel("NativeScript Extension"); - const logger = new ChannelLogger(channel); - Services.logger = logger; + Services.logger = new ChannelLogger(channel); - // Check if NativeScript CLI is installed globally and if it is compatible with the extension version - let cliVersion = Services.cli().version; - if (!cliVersion.isCompatible) { - vscode.window.showErrorMessage(cliVersion.errorMessage); + const packageJSON = vscode.extensions.getExtension("Telerik.nativescript").packageJSON; + const cliVersion = Services.cli().executeGetVersion(); + + if(!cliVersion) { + vscode.window.showErrorMessage("NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting to explicitly set the absolute path to the NativeScript CLI."); + + return; } - logExtensionInfo(logger, cliVersion.version.toString()); + if(!semver.gte(cliVersion, packageJSON.minNativescriptCliVersion)) { + vscode.window.showErrorMessage( `The existing NativeScript extension is compatible with NativeScript CLI v${packageJSON.minNativescriptCliVersion} or greater. + The currently installed NativeScript CLI is v${cliVersion}.You can update the NativeScript CLI by executing 'npm install -g nativescript'.`); + + return; + } + + Services.cliVersion = cliVersion; + Services.extensionVersion = packageJSON.version; + + logExtensionInfo(cliVersion, packageJSON); Services.analyticsService.initialize(); @@ -106,11 +118,9 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(showOutputChannelCommand); } -function logExtensionInfo(logger: ILogger, cliVersion: string): void { - const packageJSON = vscode.extensions.getExtension("Telerik.nativescript").packageJSON; - - packageJSON.version && logger.log(`Version: ${packageJSON.version}`); - packageJSON.buildVersion && logger.log(`Build version: ${packageJSON.buildVersion}`); - packageJSON.commitId && logger.log(`Commit id: ${packageJSON.commitId}`); - logger.log(`NativeScript CLI: ${cliVersion}`); +function logExtensionInfo(cliVersion: string, packageJSON: any): void { + packageJSON.version && Services.logger.log(`Version: ${packageJSON.version}`); + packageJSON.buildVersion && Services.logger.log(`Build version: ${packageJSON.buildVersion}`); + packageJSON.commitId && Services.logger.log(`Commit id: ${packageJSON.commitId}`); + Services.logger.log(`NativeScript CLI: ${cliVersion}`); } \ No newline at end of file diff --git a/src/project/androidProject.ts b/src/project/androidProject.ts index 510fc03..7aa2884 100644 --- a/src/project/androidProject.ts +++ b/src/project/androidProject.ts @@ -3,7 +3,6 @@ import * as stream from 'stream'; import {EventEmitter} from 'events'; import {Project, DebugResult} from './project'; import * as scanner from './streamScanner'; -import {Version} from '../common/version'; import {NativeScriptCli} from './nativeScriptCli'; export type GetDebugPortResult = { tnsProcess: ChildProcess, debugPort: Promise }; diff --git a/src/project/iosProject.ts b/src/project/iosProject.ts index 3f2aad4..5945c16 100644 --- a/src/project/iosProject.ts +++ b/src/project/iosProject.ts @@ -3,7 +3,6 @@ import * as stream from 'stream'; import {EventEmitter} from 'events'; import {Project, DebugResult} from './project'; import * as scanner from './streamScanner'; -import {Version} from '../common/version'; import {NativeScriptCli} from './nativeScriptCli'; export class IosProject extends Project { diff --git a/src/project/nativeScriptCli.ts b/src/project/nativeScriptCli.ts index aaf75b5..a6224b7 100644 --- a/src/project/nativeScriptCli.ts +++ b/src/project/nativeScriptCli.ts @@ -1,52 +1,11 @@ import {spawn, execSync, ChildProcess} from 'child_process'; -import {Version} from '../common/version'; import { ILogger } from '../common/logger'; import * as utils from '../common/utilities'; import * as os from 'os'; -export enum CliVersionState { - NotExisting, - OlderThanSupported, - Compatible -} - -export class CliVersion { - private _cliVersion: Version = undefined; - private _minExpectedCliVersion: Version = undefined; - private _cliVersionState: CliVersionState; - private _cliVersionErrorMessage: string; - - constructor(cliVersion: Version, minExpectedCliVersion: Version) { - this._cliVersion = cliVersion; - this._minExpectedCliVersion = minExpectedCliVersion; - - // Calculate CLI version state and CLI version error message - this._cliVersionState = CliVersionState.Compatible; - if (minExpectedCliVersion) { - if (this._cliVersion === null) { - this._cliVersionState = CliVersionState.NotExisting; - this._cliVersionErrorMessage = "NativeScript CLI not found, please run 'npm -g install nativescript' to install it."; - } - else if (this._cliVersion.compareBySubminorTo(minExpectedCliVersion) < 0) { - this._cliVersionState = CliVersionState.OlderThanSupported; - this._cliVersionErrorMessage = `The existing NativeScript extension is compatible with NativeScript CLI v${this._minExpectedCliVersion} or greater. The currently installed NativeScript CLI is v${this._cliVersion}. You can update the NativeScript CLI by executing 'npm install -g nativescript'.`; - } - } - } - - public get version() { return this._cliVersion; } - - public get state() { return this._cliVersionState; } - - public get isCompatible() { return this._cliVersionState == CliVersionState.Compatible; } - - public get errorMessage() { return this._cliVersionErrorMessage; } -} - export class NativeScriptCli { private _path: string; private _shellPath: string; - private _cliVersion: CliVersion; private _logger: ILogger; constructor(cliPath: string, logger: ILogger) { @@ -60,26 +19,18 @@ export class NativeScriptCli { if (utils.getPlatform() === utils.Platform.Windows) { this._shellPath = "cmd.exe"; } + } + + public get path(): string { return this._path; } - let versionStr = null; + public executeGetVersion(): string { try { - versionStr = this.executeSync(["--version"], undefined); + return this.executeSync(["--version"], undefined); } catch(e) { this._logger.log(e); throw new Error("NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting to explicitly set the absolute path to the NativeScript CLI."); } - let cliVersion: Version = versionStr ? Version.parse(versionStr) : null; - this._cliVersion = new CliVersion(cliVersion, utils.getMinSupportedCliVersion()); - if (!this._cliVersion.isCompatible) { - throw new Error(this._cliVersion.errorMessage); - } - } - - public get path(): string { return this._path; } - - public get version(): CliVersion { - return this._cliVersion; } public executeSync(args: string[], cwd: string): string { diff --git a/src/project/project.ts b/src/project/project.ts index 52171fb..3f64bc2 100644 --- a/src/project/project.ts +++ b/src/project/project.ts @@ -1,6 +1,5 @@ import {ChildProcess} from 'child_process'; import {EventEmitter} from 'events'; -import {Version} from '../common/version'; import {NativeScriptCli} from './nativeScriptCli'; import * as stream from 'stream'; import * as scanner from './streamScanner'; diff --git a/src/services/extensionHostServices.ts b/src/services/extensionHostServices.ts index d1cf061..62dc982 100644 --- a/src/services/extensionHostServices.ts +++ b/src/services/extensionHostServices.ts @@ -11,6 +11,9 @@ export class ExtensionHostServices extends BaseServices { private _iOSTeamService: iOSTeamService; private _analyticsService: AnalyticsService; + public cliVersion: string; + public extensionVersion: string; + public get globalState(): vscode.Memento { return this._globalState; } public set globalState(globalState: vscode.Memento) { this._globalState = globalState; } @@ -26,7 +29,7 @@ export class ExtensionHostServices extends BaseServices { } public get analyticsService(): AnalyticsService { - this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState); + this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState, this.cliVersion, this.extensionVersion); return this._analyticsService; } } From 8690456bc6f76e51be0530a8b2075ada4dbf3688 Mon Sep 17 00:00:00 2001 From: ivanovit Date: Mon, 4 Jun 2018 10:17:37 +0300 Subject: [PATCH 04/12] Integrate tslint and fixes warnings --- .vscode/settings.json | 1 + package.json | 5 +- src/analytics/analyticsBaseInfo.ts | 14 +- src/analytics/analyticsService.ts | 96 ++++++------ src/analytics/guaService.ts | 22 +-- src/common/extensionProtocol.ts | 10 +- src/common/logger.ts | 2 +- src/common/utilities.ts | 11 +- src/common/workspaceConfigService.ts | 2 +- src/debug-adapter/nativeScriptDebug.ts | 36 +---- src/debug-adapter/nativeScriptDebugAdapter.ts | 141 +++++++++--------- .../nativeScriptPathTransformer.ts | 45 +++--- .../nativeScriptTargetDiscovery.ts | 27 ++++ src/main.ts | 101 +++++++------ src/project/androidProject.ts | 49 +++--- src/project/iosProject.ts | 48 +++--- src/project/nativeScriptCli.ts | 36 +++-- src/project/project.ts | 18 +-- src/project/streamScanner.ts | 90 +++++------ src/services/channelLogger.ts | 10 +- src/services/extensionHostServices.ts | 21 +-- src/services/iOSTeamService.ts | 65 ++++---- src/services/services.ts | 5 +- tslint.json | 35 +++++ 24 files changed, 497 insertions(+), 393 deletions(-) create mode 100644 src/debug-adapter/nativeScriptTargetDiscovery.ts create mode 100644 tslint.json diff --git a/.vscode/settings.json b/.vscode/settings.json index de4c0bf..cd7cee8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.insertSpaces": true, "files.trimTrailingWhitespace": true, + "editor.renderWhitespace": "all", "files.exclude": { ".git": true, "bin": true, diff --git a/package.json b/package.json index 3345370..5ff3730 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "@types/mocha": "2.2.41", "@types/node": "6.0.46", "mocha": "2.5.3", + "tslint": "5.10.0", + "tslint-eslint-rules": "^5.3.1", "typescript": "2.6.2", "vsce": "~1.36.0", "vscode": "~1.1.10", @@ -52,7 +54,8 @@ "launch-as-server": "node --nolazy ./out/debug-adapter/nativeScriptDebug.js --server=4712", "test-mac": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/mac.json ./out/tests", "test-win": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/win.json ./out/tests", - "test-custom": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/custom.json ./out/tests" + "test-custom": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/custom.json ./out/tests", + "tslint": "tslint -p ./src/tsconfig.json -c tslint.json" }, "main": "./out/main", "activationEvents": [ diff --git a/src/analytics/analyticsBaseInfo.ts b/src/analytics/analyticsBaseInfo.ts index 0de72b5..6651382 100644 --- a/src/analytics/analyticsBaseInfo.ts +++ b/src/analytics/analyticsBaseInfo.ts @@ -2,12 +2,12 @@ export enum OperatingSystem { Windows, Linux, OSX, - Other + Other, } -export interface AnalyticsBaseInfo { - operatingSystem: OperatingSystem, - cliVersion: string, - extensionVersion: string, - clientId: string -} \ No newline at end of file +export interface IAnalyticsBaseInfo { + operatingSystem: OperatingSystem; + cliVersion: string; + extensionVersion: string; + clientId: string; +} diff --git a/src/analytics/analyticsService.ts b/src/analytics/analyticsService.ts index e1fb01b..e25dbb0 100644 --- a/src/analytics/analyticsService.ts +++ b/src/analytics/analyticsService.ts @@ -1,81 +1,91 @@ -import * as os from 'os'; -import { GUAService } from './guaService'; -import { AnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; -import { Services } from '../services/extensionHostServices'; -import * as utils from '../common/utilities'; +import * as _ from 'lodash'; +import * as uuid from 'uuid'; import * as vscode from 'vscode'; -import * as uuid from "uuid"; +import { ILogger } from '../common/logger'; +import { services } from '../services/extensionHostServices'; +import { IAnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; +import { GUAService } from './guaService'; export class AnalyticsService { - private static HAS_ANALYTICS_PROMPT_SHOWN_KEY = "nativescript.hasAnalyticsPromptShown"; - private static CLIENT_ID_KEY = "nativescript.analyticsClientId"; + private static HAS_ANALYTICS_PROMPT_SHOWN_KEY = 'nativescript.hasAnalyticsPromptShown'; + private static CLIENT_ID_KEY = 'nativescript.analyticsClientId'; + private static DOCS_LINK = 'https://github.com/NativeScript/nativescript-vscode-extension/blob/master/README.md#how-to-disable-the-analytics'; private static ANALYTICS_PROMPT_MESSAGE = `Help us improve the NativeScript extension by allowing Progress to collect anonymous usage data. - For more information about the gathered information and how it is used, read our [privacy statement](https://www.progress.com/legal/privacy-policy). - You can [disable the analytics and data collection](https://github.com/NativeScript/nativescript-vscode-extension/blob/master/README.md#how-to-disable-the-analytics) at any given time. + For more information about the gathered information and how it is used, + read our [privacy statement](https://www.progress.com/legal/privacy-policy). + You can [disable the analytics and data collection](${AnalyticsService.DOCS_LINK}) at any given time. Do you want to enable analytics?`; - private static ANALYTICS_PROMPT_ACCEPT_ACTION = "Yes"; - private static ANALYTICS_PROMPT_DENY_ACTION = "No"; + + private static ANALYTICS_PROMPT_ACCEPT_ACTION = 'Yes'; + private static ANALYTICS_PROMPT_DENY_ACTION = 'No'; + + private static getOperatingSystem(): OperatingSystem { + switch (process.platform) { + case 'win32': + return OperatingSystem.Windows; + case 'darwin': + return OperatingSystem.OSX; + case 'linux': + case 'freebsd': + return OperatingSystem.Linux; + default: + return OperatingSystem.Other; + } + } private _globalState: vscode.Memento; - private _baseInfo: AnalyticsBaseInfo; + private _logger: ILogger; + private _baseInfo: IAnalyticsBaseInfo; private _gua: GUAService; private _analyticsEnabled: boolean; - constructor(globalState: vscode.Memento, cliVersion: string, extensionVersion: string) { + constructor(globalState: vscode.Memento, cliVersion: string, extensionVersion: string, logger: ILogger) { this._globalState = globalState; + this._logger = logger; vscode.workspace.onDidChangeConfiguration(() => this.updateAnalyticsEnabled()); this._baseInfo = { cliVersion, + clientId: this.getOrGenerateClientId(), extensionVersion, operatingSystem: AnalyticsService.getOperatingSystem(), - clientId: this.getOrGenerateClientId() }; } public launchDebugger(request: string, platform: string): Promise { - if(this._analyticsEnabled) { + if (this._analyticsEnabled) { try { return this._gua.launchDebugger(request, platform); - } catch(e) {} + } catch (e) { + this._logger.log(`Analytics error: ${_.isString(e) ? e : e.message}`); + } } return Promise.resolve(); } public runRunCommand(platform: string): Promise { - if(this._analyticsEnabled) { + if (this._analyticsEnabled) { try { return this._gua.runRunCommand(platform); - } catch(e) { } + } catch (e) { + this._logger.log(`Analytics error: ${_.isString(e) ? e : e.message}`); + } } return Promise.resolve(); } - private static getOperatingSystem() : OperatingSystem { - switch(process.platform) { - case 'win32': - return OperatingSystem.Windows; - case 'darwin': - return OperatingSystem.OSX; - case 'linux': - case 'freebsd': - return OperatingSystem.Linux; - default: - return OperatingSystem.Other; - }; - } - - public initialize() : void { + public initialize(): void { const hasAnalyticsPromptShown = this._globalState.get(AnalyticsService.HAS_ANALYTICS_PROMPT_SHOWN_KEY); - if(!hasAnalyticsPromptShown) { + + if (!hasAnalyticsPromptShown) { vscode.window.showInformationMessage(AnalyticsService.ANALYTICS_PROMPT_MESSAGE, AnalyticsService.ANALYTICS_PROMPT_ACCEPT_ACTION, - AnalyticsService.ANALYTICS_PROMPT_DENY_ACTION + AnalyticsService.ANALYTICS_PROMPT_DENY_ACTION, ) - .then(result => this.onAnalyticsMessageConfirmation(result)); + .then((result) => this.onAnalyticsMessageConfirmation(result)); return; } @@ -86,7 +96,7 @@ export class AnalyticsService { private getOrGenerateClientId(): string { let clientId = this._globalState.get(AnalyticsService.CLIENT_ID_KEY); - if(!clientId) { + if (!clientId) { clientId = uuid.v4(); this._globalState.update(AnalyticsService.CLIENT_ID_KEY, clientId); } @@ -94,20 +104,20 @@ export class AnalyticsService { return clientId; } - private onAnalyticsMessageConfirmation(result: string) : void { + private onAnalyticsMessageConfirmation(result: string): void { const shouldEnableAnalytics = result === AnalyticsService.ANALYTICS_PROMPT_ACCEPT_ACTION ? true : false; this._globalState.update(AnalyticsService.HAS_ANALYTICS_PROMPT_SHOWN_KEY, true); - Services.workspaceConfigService.isAnalyticsEnabled = shouldEnableAnalytics; + services.workspaceConfigService.isAnalyticsEnabled = shouldEnableAnalytics; this.updateAnalyticsEnabled(); } private updateAnalyticsEnabled() { - this._analyticsEnabled = Services.workspaceConfigService.isAnalyticsEnabled; + this._analyticsEnabled = services.workspaceConfigService.isAnalyticsEnabled; - if(this._analyticsEnabled && !this._gua) { + if (this._analyticsEnabled && !this._gua) { this._gua = new GUAService('UA-111455-29', this._baseInfo); } } -} \ No newline at end of file +} diff --git a/src/analytics/guaService.ts b/src/analytics/guaService.ts index 6d50f49..8ecf4e4 100644 --- a/src/analytics/guaService.ts +++ b/src/analytics/guaService.ts @@ -1,5 +1,5 @@ import * as ua from 'universal-analytics'; -import { AnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; +import { IAnalyticsBaseInfo, OperatingSystem } from './analyticsBaseInfo'; /** * Google Universal Analytics Service @@ -8,38 +8,42 @@ export class GUAService { private _visitor: any; private _getBasePayload: () => any; - constructor(trackingId: string, baseInfo: AnalyticsBaseInfo) { + constructor(trackingId: string, baseInfo: IAnalyticsBaseInfo) { this._visitor = ua(trackingId, baseInfo.clientId, { requestOptions: {}, strictCidFormat: false }); this._getBasePayload = () => { return { - cid: baseInfo.clientId, - dh: 'ns-vs-extension.org', cd5: baseInfo.cliVersion, cd6: OperatingSystem[baseInfo.operatingSystem], - cd7: baseInfo.extensionVersion + cd7: baseInfo.extensionVersion, + cid: baseInfo.clientId, + dh: 'ns-vs-extension.org', }; }; } public launchDebugger(request: string, platform: string): Promise { - let payload = this._getBasePayload(); + const payload = this._getBasePayload(); + payload.ec = 'vscode-extension-debug'; // event category payload.ea = `debug-${request}-on-${platform}`; // event action + return this.sendEvent(payload); } public runRunCommand(platform: string): Promise { - let payload = this._getBasePayload(); + const payload = this._getBasePayload(); + payload.ec = 'vscode-extension-command'; // event category payload.ea = `command-run-on-${platform}`; // event action + return this.sendEvent(payload); } private sendEvent(params): Promise { return new Promise((res, rej) => { - this._visitor.event(params, err => { + this._visitor.event(params, (err) => { return err ? rej(err) : res(); }); }); } -} \ No newline at end of file +} diff --git a/src/common/extensionProtocol.ts b/src/common/extensionProtocol.ts index 0c59f26..67bc1b7 100644 --- a/src/common/extensionProtocol.ts +++ b/src/common/extensionProtocol.ts @@ -1,14 +1,14 @@ -export interface Request { +export interface IRequest { id: string; service: string; method: string; args: any[]; } -export interface Response { +export interface IResponse { requestId: string; - result: Object; + result: object; } -export const BEFORE_DEBUG_START = "before-debug-start"; -export const NS_DEBUG_ADAPTER_MESSAGE = "ns-debug-adapter-message"; \ No newline at end of file +export const BEFORE_DEBUG_START = 'before-debug-start'; +export const NS_DEBUG_ADAPTER_MESSAGE = 'ns-debug-adapter-message'; diff --git a/src/common/logger.ts b/src/common/logger.ts index 733285d..215398e 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -10,4 +10,4 @@ interface ILogger { log(msg: string, level?: LogLevel): void; } -export { ILogger, LogLevel } \ No newline at end of file +export { ILogger, LogLevel }; diff --git a/src/common/utilities.ts b/src/common/utilities.ts index 6716215..dfde8c8 100644 --- a/src/common/utilities.ts +++ b/src/common/utilities.ts @@ -1,24 +1,25 @@ -import {ChildProcess, exec} from 'child_process'; +import { ChildProcess, exec } from 'child_process'; import * as os from 'os'; -export function killProcess(childProcess: ChildProcess) : void { +export function killProcess(childProcess: ChildProcess): void { switch (process.platform) { - case "win32": + case 'win32': exec(`taskkill /pid ${childProcess.pid} /T /F`); break; default: - childProcess.kill("SIGINT"); + childProcess.kill('SIGINT'); break; } } export const enum Platform { - Windows, OSX, Linux + Windows, OSX, Linux, } export function getPlatform(): Platform { const platform = os.platform(); + return platform === 'darwin' ? Platform.OSX : platform === 'win32' ? Platform.Windows : Platform.Linux; diff --git a/src/common/workspaceConfigService.ts b/src/common/workspaceConfigService.ts index 862ef29..89f82af 100644 --- a/src/common/workspaceConfigService.ts +++ b/src/common/workspaceConfigService.ts @@ -12,4 +12,4 @@ export class WorkspaceConfigService { public get tnsPath(): string { return vscode.workspace.getConfiguration('nativescript').get('tnsPath') as string; } -} \ No newline at end of file +} diff --git a/src/debug-adapter/nativeScriptDebug.ts b/src/debug-adapter/nativeScriptDebug.ts index dac102d..20e02fe 100644 --- a/src/debug-adapter/nativeScriptDebug.ts +++ b/src/debug-adapter/nativeScriptDebug.ts @@ -1,43 +1,21 @@ -import { chromeConnection, chromeTargetDiscoveryStrategy, ChromeDebugAdapter, telemetry, logger, ChromeDebugSession, UrlPathTransformer } from 'vscode-chrome-debug-core'; -import * as path from 'path'; import * as os from 'os'; +import * as path from 'path'; +import { chromeConnection, ChromeDebugSession } from 'vscode-chrome-debug-core'; import { NativeScriptDebugAdapter } from './nativeScriptDebugAdapter'; import { NativeScriptPathTransformer } from './nativeScriptPathTransformer'; -import * as uuid from "uuid"; - -class Discovery extends chromeTargetDiscoveryStrategy.ChromeTargetDiscovery { - constructor() { - super(logger, new telemetry.TelemetryReporter()) - } - - public getTarget(address: string, port: number, targetFilter?: any, targetUrl?: string): Promise { - return Promise.resolve({ - webSocketDebuggerUrl: `ws://${address}:${port}`, - description: "NS Debug Target", - id: uuid.v4(), - title: "NS Debug Target", - type: "node", - devtoolsFrontendUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=${address}:${port}` - }); - } - - async getAllTargets(address: string, port: number, targetFilter?: chromeConnection.ITargetFilter, targetUrl?: string): Promise { - const target = await this.getTarget(address, port); - return Promise.resolve([ target ]); - } -} +import { NativeScriptTargetDiscovery } from './nativeScriptTargetDiscovery'; class NSAndroidConnection extends chromeConnection.ChromeConnection { constructor() { - super(new Discovery()); + super(new NativeScriptTargetDiscovery()); } } ChromeDebugSession.run(ChromeDebugSession.getSession( { - logFilePath: path.join(os.tmpdir(), 'nativescript-extension.txt'), adapter: NativeScriptDebugAdapter, - extensionName: 'nativescript-extension', chromeConnection: NSAndroidConnection, - pathTransformer: NativeScriptPathTransformer + extensionName: 'nativescript-extension', + logFilePath: path.join(os.tmpdir(), 'nativescript-extension.txt'), + pathTransformer: NativeScriptPathTransformer, })); diff --git a/src/debug-adapter/nativeScriptDebugAdapter.ts b/src/debug-adapter/nativeScriptDebugAdapter.ts index 9a50ab3..e96b1b9 100644 --- a/src/debug-adapter/nativeScriptDebugAdapter.ts +++ b/src/debug-adapter/nativeScriptDebugAdapter.ts @@ -1,27 +1,26 @@ -import { ChromeDebugAdapter, logger } from 'vscode-chrome-debug-core'; -import { OutputEvent, TerminatedEvent, Event } from 'vscode-debugadapter'; -import * as utils from '../common/utilities'; -import {IosProject} from '../project/iosProject'; -import {AndroidProject} from '../project/androidProject'; import { ChildProcess } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import {DebugResult} from '../project/project'; +import { ChromeDebugAdapter, logger } from 'vscode-chrome-debug-core'; +import { Event, OutputEvent, TerminatedEvent } from 'vscode-debugadapter'; import * as extProtocol from '../common/extensionProtocol'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import {NativeScriptCli} from '../project/nativeScriptCli'; +import * as utils from '../common/utilities'; +import { AndroidProject } from '../project/androidProject'; +import { IosProject } from '../project/iosProject'; +import { NativeScriptCli } from '../project/nativeScriptCli'; +import { IDebugResult } from '../project/project'; export class NativeScriptDebugAdapter extends ChromeDebugAdapter { private _tnsProcess: ChildProcess; private _idCounter = 0; - private _pendingRequests: Object= {}; + private _pendingRequests: object = {}; - public async attach(args: any): Promise { - return await this.processRequestAndAttach(args); + public attach(args: any): Promise { + return this.processRequestAndAttach(args); } - public async launch(args: any): Promise { - return await this.processRequestAndAttach(args); + public launch(args: any): Promise { + return this.processRequestAndAttach(args); } public disconnect(args: any): void { @@ -35,20 +34,21 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { } } + public onExtensionResponse(response) { + this._pendingRequests[response.requestId](response.result); + delete this._pendingRequests[response.requestId]; + } + private async processRequestAndAttach(args: any) { const transformedArgs = await this.processRequest(args); + (this.pathTransformer as any).setTargetPlatform(args.platform); (ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000; return super.attach(transformedArgs); } - public onExtensionResponse(response) { - this._pendingRequests[response.requestId](response.result); - delete this._pendingRequests[response.requestId]; - } - - private async processRequest(args: any) : Promise { + private async processRequest(args: any): Promise { args = this.translateArgs(args); this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START)); @@ -56,7 +56,7 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { const tnsPath = await this.callRemoteMethod('workspaceConfigService', 'tnsPath'); const cli = new NativeScriptCli(tnsPath, logger); - const project = args.platform == "ios" ? + const project = args.platform === 'ios' ? new IosProject(args.appRoot, cli) : new AndroidProject(args.appRoot, cli); @@ -64,20 +64,23 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { // Run CLI Command const version = project.cli.executeGetVersion(); + this.log(`[NSDebugAdapter] Using tns CLI v${version} on path '${project.cli.path}'\n`); this.log('[NSDebugAdapter] Running tns command...\n'); - let cliCommand: DebugResult; + let cliCommand: IDebugResult; - if (args.request === "launch") { + if (args.request === 'launch') { let tnsArgs = args.tnsArgs; // For iOS the TeamID is required if there's more than one. // Therefore if not set, show selection to the user. - if(args.platform && args.platform.toLowerCase() === 'ios') { - let teamId = this.getTeamId(path.join(args.appRoot, 'app'), tnsArgs); - if(!teamId) { - let selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam'); - if(selectedTeam) { + if (args.platform && args.platform.toLowerCase() === 'ios') { + const teamId = this.getTeamId(path.join(args.appRoot, 'app'), tnsArgs); + + if (!teamId) { + const selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam'); + + if (selectedTeam) { // add the selected by the user Team Id tnsArgs = (tnsArgs || []).concat(['--teamId', selectedTeam.id]); this.log(`[NSDebugAdapter] Using iOS Team ID '${selectedTeam.id}', you can change this in the workspace settings.\n`); @@ -86,22 +89,21 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { } cliCommand = project.debug({ stopOnEntry: args.stopOnEntry, watch: args.watch }, tnsArgs); - } - else if (args.request === "attach") { + } else if (args.request === 'attach') { cliCommand = project.attach(args.tnsArgs); } if (cliCommand.tnsProcess) { this._tnsProcess = cliCommand.tnsProcess; - cliCommand.tnsProcess.stdout.on('data', data => { this.log(data.toString()); }); - cliCommand.tnsProcess.stderr.on('data', data => { this.log(data.toString()); }); + cliCommand.tnsProcess.stdout.on('data', (data) => { this.log(data.toString()); }); + cliCommand.tnsProcess.stderr.on('data', (data) => { this.log(data.toString()); }); cliCommand.tnsProcess.on('close', (code, signal) => { this.log(`[NSDebugAdapter] The tns command finished its execution with code ${code}.\n`); // Sometimes we execute "tns debug android --start" and the process finishes // which is totally fine. If there's an error we need to Terminate the session. - if(code !== 0) { + if (code !== 0) { this.log(`The tns command finished its execution with code ${code}`); this._session.sendEvent(new TerminatedEvent()); } @@ -121,11 +123,11 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { } private translateArgs(args): any { - if(args.diagnosticLogging) { + if (args.diagnosticLogging) { args.trace = args.diagnosticLogging; } - if(args.appRoot) { + if (args.appRoot) { args.webRoot = args.appRoot; } @@ -138,16 +140,18 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { private getTeamId(appRoot: string, tnsArgs?: string[]): string { // try to get the TeamId from the TnsArgs - if(tnsArgs) { + if (tnsArgs) { const teamIdArgIndex = tnsArgs.indexOf('--teamId'); - if(teamIdArgIndex > 0 && teamIdArgIndex + 1 < tnsArgs.length) { + + if (teamIdArgIndex > 0 && teamIdArgIndex + 1 < tnsArgs.length) { return tnsArgs[ teamIdArgIndex + 1 ]; } } // try to get the TeamId from the buildxcconfig or teamid file const teamIdFromConfig = this.readTeamId(appRoot); - if(teamIdFromConfig) { + + if (teamIdFromConfig) { return teamIdFromConfig; } @@ -156,38 +160,41 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { } private readXCConfig(appRoot: string, flag: string): string { - let xcconfigFile = path.join(appRoot, "App_Resources/iOS/build.xcconfig"); - if (fs.existsSync(xcconfigFile)) { - let text = fs.readFileSync(xcconfigFile, { encoding: 'utf8'}); - let teamId: string; - text.split(/\r?\n/).forEach((line) => { - line = line.replace(/\/(\/)[^\n]*$/, ""); - if (line.indexOf(flag) >= 0) { - teamId = line.split("=")[1].trim(); - if (teamId[teamId.length - 1] === ';') { - teamId = teamId.slice(0, -1); - } - } - }); - if (teamId) { - return teamId; - } - } - - let fileName = path.join(appRoot, "teamid"); - if (fs.existsSync(fileName)) { - return fs.readFileSync(fileName, { encoding: 'utf8' }); - } - - return null; - } - - private readTeamId(appRoot): string { - return this.readXCConfig(appRoot, "DEVELOPMENT_TEAM"); + const xcconfigFile = path.join(appRoot, 'App_Resources/iOS/build.xcconfig'); + + if (fs.existsSync(xcconfigFile)) { + const text = fs.readFileSync(xcconfigFile, { encoding: 'utf8'}); + let teamId: string; + + text.split(/\r?\n/).forEach((line) => { + line = line.replace(/\/(\/)[^\n]*$/, ''); + if (line.indexOf(flag) >= 0) { + teamId = line.split('=')[1].trim(); + if (teamId[teamId.length - 1] === ';') { + teamId = teamId.slice(0, -1); + } + } + }); + if (teamId) { + return teamId; + } + } + + const fileName = path.join(appRoot, 'teamid'); + + if (fs.existsSync(fileName)) { + return fs.readFileSync(fileName, { encoding: 'utf8' }); + } + + return null; + } + + private readTeamId(appRoot): string { + return this.readXCConfig(appRoot, 'DEVELOPMENT_TEAM'); } private callRemoteMethod(service: string, method: string, ...args: any[]): Promise { - let request: extProtocol.Request = { id: `req${++this._idCounter}`, service: service, method: method, args: args }; + const request: extProtocol.IRequest = { id: `req${++this._idCounter}`, service, method, args }; return new Promise((res, rej) => { this._pendingRequests[request.id] = res; @@ -195,4 +202,4 @@ export class NativeScriptDebugAdapter extends ChromeDebugAdapter { this._session.sendEvent(new Event(extProtocol.NS_DEBUG_ADAPTER_MESSAGE, request)); }); } -} \ No newline at end of file +} diff --git a/src/debug-adapter/nativeScriptPathTransformer.ts b/src/debug-adapter/nativeScriptPathTransformer.ts index 3e6e7dd..49237f3 100644 --- a/src/debug-adapter/nativeScriptPathTransformer.ts +++ b/src/debug-adapter/nativeScriptPathTransformer.ts @@ -1,12 +1,12 @@ -import * as path from 'path'; import * as fs from 'fs'; -import { UrlPathTransformer } from 'vscode-chrome-debug-core'; import * as _ from 'lodash'; +import * as path from 'path'; +import { UrlPathTransformer } from 'vscode-chrome-debug-core'; export class NativeScriptPathTransformer extends UrlPathTransformer { private filePatterns = { - android: new RegExp("^(file:)?/*data/(data|user/\\d+)/.*?/files/(.*)$", "i"), - ios: new RegExp("^(file:)?/*(.*)$", "i") + android: new RegExp('^(file:)?/*data/(data|user/\\d+)/.*?/files/(.*)$', 'i'), + ios: new RegExp('^(file:)?/*(.*)$', 'i'), }; private targetPlatform: string; @@ -16,51 +16,48 @@ export class NativeScriptPathTransformer extends UrlPathTransformer { } protected async targetUrlToClientPath(webRoot: string, scriptUrl: string): Promise { - if (!scriptUrl) - { + if (!scriptUrl) { return; } - if (_.startsWith(scriptUrl, "mdha:")) - { - scriptUrl = _.trimStart(scriptUrl, "mdha:"); + if (_.startsWith(scriptUrl, 'mdha:')) { + scriptUrl = _.trimStart(scriptUrl, 'mdha:'); } - if (path.isAbsolute(scriptUrl) && fs.existsSync(scriptUrl)) - { + if (path.isAbsolute(scriptUrl) && fs.existsSync(scriptUrl)) { return Promise.resolve(scriptUrl); } const filePattern = this.filePatterns[this.targetPlatform]; - const matches = filePattern.exec(scriptUrl) + const matches = filePattern.exec(scriptUrl); let relativePath = scriptUrl; - if(matches) { + + if (matches) { relativePath = this.targetPlatform === 'android' ? matches[3] : matches[2]; } - const nodePath = path.join("..", "node_modules"); - relativePath = relativePath.replace("tns_modules", nodePath); + const nodePath = path.join('..', 'node_modules'); + + relativePath = relativePath.replace('tns_modules', nodePath); const absolutePath = path.resolve(path.join(webRoot, relativePath)); - if (fs.existsSync(absolutePath)) - { + if (fs.existsSync(absolutePath)) { return Promise.resolve(absolutePath); } const fileExtension = path.extname(absolutePath); - if (fileExtension) - { - const platformSpecificAbsolutePath = absolutePath.replace(fileExtension, `.${this.targetPlatform}${fileExtension}`); - if (fs.existsSync(platformSpecificAbsolutePath)) - { - return Promise.resolve(platformSpecificAbsolutePath); + if (fileExtension) { + const platformSpecificPath = absolutePath.replace(fileExtension, `.${this.targetPlatform}${fileExtension}`); + + if (fs.existsSync(platformSpecificPath)) { + return Promise.resolve(platformSpecificPath); } } return Promise.resolve(scriptUrl); } -} \ No newline at end of file +} diff --git a/src/debug-adapter/nativeScriptTargetDiscovery.ts b/src/debug-adapter/nativeScriptTargetDiscovery.ts new file mode 100644 index 0000000..7dfa821 --- /dev/null +++ b/src/debug-adapter/nativeScriptTargetDiscovery.ts @@ -0,0 +1,27 @@ +import * as uuid from 'uuid'; +import { chromeConnection, chromeTargetDiscoveryStrategy, logger, telemetry } from 'vscode-chrome-debug-core'; + +export class NativeScriptTargetDiscovery extends chromeTargetDiscoveryStrategy.ChromeTargetDiscovery { + constructor() { + super(logger, new telemetry.TelemetryReporter()); + } + + public getTarget(address: string, port: number, targetFilter?: any, targetUrl?: string): Promise { + return Promise.resolve({ + description: 'NS Debug Target', + devtoolsFrontendUrl: `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=${address}:${port}`, + id: uuid.v4(), + title: 'NS Debug Target', + type: 'node', + webSocketDebuggerUrl: `ws://${address}:${port}`, + }); + } + + public async getAllTargets(address: string, + port: number, targetFilter?: chromeConnection.ITargetFilter, + targetUrl?: string): Promise { + const target = await this.getTarget(address, port); + + return Promise.resolve([ target ]); + } +} diff --git a/src/main.ts b/src/main.ts index 15f6346..a4e8b25 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,115 +1,118 @@ +import * as semver from 'semver'; import * as vscode from 'vscode'; -import {Services} from './services/extensionHostServices'; -import {Project} from './project/project'; -import {IosProject} from './project/iosProject'; -import {AndroidProject} from './project/androidProject'; -import * as utils from './common/utilities'; import * as extProtocol from './common/extensionProtocol'; +import * as utils from './common/utilities'; +import { AndroidProject } from './project/androidProject'; +import { IosProject } from './project/iosProject'; +import { Project } from './project/project'; import { ChannelLogger } from './services/channelLogger'; -import { ILogger } from './common/logger'; -import * as semver from "semver"; +import { services } from './services/extensionHostServices'; // this method is called when the extension is activated export function activate(context: vscode.ExtensionContext) { - Services.globalState = context.globalState; - Services.cliPath = Services.workspaceConfigService.tnsPath || Services.cliPath; + services.globalState = context.globalState; + services.cliPath = services.workspaceConfigService.tnsPath || services.cliPath; + + const channel = vscode.window.createOutputChannel('NativeScript Extension'); - const channel = vscode.window.createOutputChannel("NativeScript Extension"); - Services.logger = new ChannelLogger(channel); + services.logger = new ChannelLogger(channel); - const packageJSON = vscode.extensions.getExtension("Telerik.nativescript").packageJSON; - const cliVersion = Services.cli().executeGetVersion(); + const packageJSON = vscode.extensions.getExtension('Telerik.nativescript').packageJSON; + const cliVersion = services.cli().executeGetVersion(); - if(!cliVersion) { + if (!cliVersion) { + // tslint:disable-next-line:max-line-length vscode.window.showErrorMessage("NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting to explicitly set the absolute path to the NativeScript CLI."); return; } - if(!semver.gte(cliVersion, packageJSON.minNativescriptCliVersion)) { - vscode.window.showErrorMessage( `The existing NativeScript extension is compatible with NativeScript CLI v${packageJSON.minNativescriptCliVersion} or greater. + if (!semver.gte(cliVersion, packageJSON.minNativescriptCliVersion)) { + // tslint:disable-next-line:max-line-length + vscode.window.showErrorMessage(`The existing NativeScript extension is compatible with NativeScript CLI v${packageJSON.minNativescriptCliVersion} or greater. The currently installed NativeScript CLI is v${cliVersion}.You can update the NativeScript CLI by executing 'npm install -g nativescript'.`); return; } - Services.cliVersion = cliVersion; - Services.extensionVersion = packageJSON.version; + services.cliVersion = cliVersion; + services.extensionVersion = packageJSON.version; logExtensionInfo(cliVersion, packageJSON); - Services.analyticsService.initialize(); + services.analyticsService.initialize(); - let showOutputChannelCommand = vscode.commands.registerCommand('nativescript.showOutputChannel', () => { + const showOutputChannelCommand = vscode.commands.registerCommand('nativescript.showOutputChannel', () => { channel.show(); }); - let beforeBuildDisposables = new Array(); - let runCommand = (project: Project) => { + const beforeBuildDisposables = new Array(); + const runCommand = (project: Project) => { if (vscode.workspace.rootPath === undefined) { vscode.window.showErrorMessage('No workspace opened.'); + return; } // Show output channel - let runChannel: vscode.OutputChannel = vscode.window.createOutputChannel(`Run on ${project.platformName()}`); + const runChannel: vscode.OutputChannel = vscode.window.createOutputChannel(`Run on ${project.platformName()}`); runChannel.clear(); runChannel.show(vscode.ViewColumn.Two); - Services.analyticsService.runRunCommand(project.platformName()); + services.analyticsService.runRunCommand(project.platformName()); - let tnsProcess = project.run(); - tnsProcess.on('error', err => { + const tnsProcess = project.run(); + tnsProcess.on('error', (err) => { vscode.window.showErrorMessage('Unexpected error executing NativeScript Run command.'); }); - tnsProcess.stderr.on('data', data => { + tnsProcess.stderr.on('data', (data) => { runChannel.append(data.toString()); }); - tnsProcess.stdout.on('data', data => { + tnsProcess.stdout.on('data', (data) => { runChannel.append(data.toString()); }); - tnsProcess.on('exit', exitCode => { + tnsProcess.on('exit', (exitCode) => { tnsProcess.stdout.removeAllListeners('data'); tnsProcess.stderr.removeAllListeners('data'); }); - tnsProcess.on('close', exitCode => { + tnsProcess.on('close', (exitCode) => { runChannel.hide(); }); const disposable = { - dispose: () => utils.killProcess(tnsProcess) + dispose: () => utils.killProcess(tnsProcess), }; context.subscriptions.push(disposable); beforeBuildDisposables.push(disposable); }; - let runIosCommand = vscode.commands.registerCommand('nativescript.runIos', () => { - return runCommand(new IosProject(vscode.workspace.rootPath, Services.cli())); + const runIosCommand = vscode.commands.registerCommand('nativescript.runIos', () => { + return runCommand(new IosProject(vscode.workspace.rootPath, services.cli())); }); - let runAndroidCommand = vscode.commands.registerCommand('nativescript.runAndroid', () => { - return runCommand(new AndroidProject(vscode.workspace.rootPath, Services.cli())); + const runAndroidCommand = vscode.commands.registerCommand('nativescript.runAndroid', () => { + return runCommand(new AndroidProject(vscode.workspace.rootPath, services.cli())); }); - context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent(event => { - if(event.event === extProtocol.BEFORE_DEBUG_START) { - beforeBuildDisposables.forEach(disposable => disposable.dispose()); + context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((event) => { + if (event.event === extProtocol.BEFORE_DEBUG_START) { + beforeBuildDisposables.forEach((disposable) => disposable.dispose()); } - if(event.event === extProtocol.NS_DEBUG_ADAPTER_MESSAGE) { - const request = event.body as extProtocol.Request; - const service = Services[request.service]; + if (event.event === extProtocol.NS_DEBUG_ADAPTER_MESSAGE) { + const request = event.body as extProtocol.IRequest; + const service = services[request.service]; const method = service[request.method]; const response = typeof method === 'function' ? service[request.method].call(service, ...request.args) : method; - if(response.then) { - response.then(actualResponse => event.session.customRequest("onExtensionResponse", { requestId: request.id, result: actualResponse })); + if (response.then) { + response.then((result) => event.session.customRequest('onExtensionResponse', { requestId: request.id, result })); return; } - event.session.customRequest("onExtensionResponse", { requestId: request.id, result: response }) + event.session.customRequest('onExtensionResponse', { requestId: request.id, result: response }); } })); @@ -119,8 +122,8 @@ export function activate(context: vscode.ExtensionContext) { } function logExtensionInfo(cliVersion: string, packageJSON: any): void { - packageJSON.version && Services.logger.log(`Version: ${packageJSON.version}`); - packageJSON.buildVersion && Services.logger.log(`Build version: ${packageJSON.buildVersion}`); - packageJSON.commitId && Services.logger.log(`Commit id: ${packageJSON.commitId}`); - Services.logger.log(`NativeScript CLI: ${cliVersion}`); -} \ No newline at end of file + packageJSON.version && services.logger.log(`Version: ${packageJSON.version}`); + packageJSON.buildVersion && services.logger.log(`Build version: ${packageJSON.buildVersion}`); + packageJSON.commitId && services.logger.log(`Commit id: ${packageJSON.commitId}`); + services.logger.log(`NativeScript CLI: ${cliVersion}`); +} diff --git a/src/project/androidProject.ts b/src/project/androidProject.ts index 7aa2884..c975e65 100644 --- a/src/project/androidProject.ts +++ b/src/project/androidProject.ts @@ -1,11 +1,9 @@ -import {ChildProcess} from 'child_process'; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; import * as stream from 'stream'; -import {EventEmitter} from 'events'; -import {Project, DebugResult} from './project'; +import { NativeScriptCli } from './nativeScriptCli'; +import { IDebugResult, Project } from './project'; import * as scanner from './streamScanner'; -import {NativeScriptCli} from './nativeScriptCli'; - -export type GetDebugPortResult = { tnsProcess: ChildProcess, debugPort: Promise }; export class AndroidProject extends Project { @@ -14,45 +12,52 @@ export class AndroidProject extends Project { } public platformName(): string { - return "android"; + return 'android'; } - public attach(tnsArgs?: string[]): DebugResult { - let args: string[] = ["--start"]; + public attach(tnsArgs?: string[]): IDebugResult { + let args: string[] = ['--start']; + args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter, true); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } - public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): DebugResult { + public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): IDebugResult { let args: string[] = []; - args.push(options.watch ? "--watch" : "--no-watch"); - if (options.stopOnEntry) { args.push("--debug-brk"); } + + args.push(options.watch ? '--watch' : '--no-watch'); + if (options.stopOnEntry) { args.push('--debug-brk'); } args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter, false); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } protected configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter, attach?: boolean): void { super.configureReadyEvent(readableStream, eventEmitter); let debugPort = null; - new scanner.StringMatchingScanner(readableStream).onEveryMatch(new RegExp("device: .* debug port: [0-9]+"), (match: scanner.MatchFound) => { - //device: {device-name} debug port: {debug-port} - debugPort = parseInt((match.matches[0]).match("(?:debug port: )([\\d]{5})")[1]); + + new scanner.StringMatchingScanner(readableStream).onEveryMatch(new RegExp('device: .* debug port: [0-9]+'), (match: scanner.IMatchFound) => { + // device: {device-name} debug port: {debug-port} + debugPort = parseInt((match.matches[0] as string).match('(?:debug port: )([\\d]{5})')[1], 10); if (attach) { // wait a little before trying to connect, this gives a chance for adb to be able to connect to the debug socket setTimeout(() => { eventEmitter.emit('readyForConnection', debugPort); }, 1000); } }); if (!attach) { - new scanner.StringMatchingScanner(readableStream).onEveryMatch('# NativeScript Debugger started #', (match: scanner.MatchFound) => { + new scanner.StringMatchingScanner(readableStream).onEveryMatch('# NativeScript Debugger started #', (match: scanner.IMatchFound) => { // wait a little before trying to connect, this gives a chance for adb to be able to connect to the debug socket setTimeout(() => { eventEmitter.emit('readyForConnection', debugPort); }, 1000); }); diff --git a/src/project/iosProject.ts b/src/project/iosProject.ts index 5945c16..9cf77f3 100644 --- a/src/project/iosProject.ts +++ b/src/project/iosProject.ts @@ -1,9 +1,9 @@ -import {ChildProcess} from 'child_process'; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; import * as stream from 'stream'; -import {EventEmitter} from 'events'; -import {Project, DebugResult} from './project'; +import { NativeScriptCli } from './nativeScriptCli'; +import { IDebugResult, Project } from './project'; import * as scanner from './streamScanner'; -import {NativeScriptCli} from './nativeScriptCli'; export class IosProject extends Project { @@ -16,38 +16,46 @@ export class IosProject extends Project { } public platformName(): string { - return "ios"; + return 'ios'; } - public attach(tnsArgs?: string[]): DebugResult { - let args: string[] = ["--start"]; + public attach(tnsArgs?: string[]): IDebugResult { + let args: string[] = ['--start']; + args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } - public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): DebugResult { + public debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): IDebugResult { let args: string[] = []; - args.push(options.watch ? "--watch" : "--no-watch"); - if (options.stopOnEntry) { args.push("--debug-brk"); } + + args.push(options.watch ? '--watch' : '--no-watch'); + if (options.stopOnEntry) { args.push('--debug-brk'); } args = args.concat(tnsArgs); - let debugProcess : ChildProcess = super.executeDebugCommand(args); - let tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + const debugProcess: ChildProcess = super.executeDebugCommand(args); + const tnsOutputEventEmitter: EventEmitter = new EventEmitter(); + this.configureReadyEvent(debugProcess.stdout, tnsOutputEventEmitter); - return { tnsProcess: debugProcess, tnsOutputEventEmitter: tnsOutputEventEmitter }; + + return { tnsProcess: debugProcess, tnsOutputEventEmitter }; } protected configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter): void { super.configureReadyEvent(readableStream, eventEmitter); - let streamScanner = new scanner.StringMatchingScanner(readableStream); - streamScanner.onEveryMatch(new RegExp("Opened localhost (.*)"), (match: scanner.MatchFound) => { - let port = parseInt(match.matches[1]); - setTimeout(() => { eventEmitter.emit('readyForConnection', port) }, 1000); + const streamScanner = new scanner.StringMatchingScanner(readableStream); + + streamScanner.onEveryMatch(new RegExp('Opened localhost (.*)'), (match: scanner.IMatchFound) => { + const port = parseInt(match.matches[1] as string, 10); + + setTimeout(() => { eventEmitter.emit('readyForConnection', port); }, 1000); }); } diff --git a/src/project/nativeScriptCli.ts b/src/project/nativeScriptCli.ts index a6224b7..3cddcd2 100644 --- a/src/project/nativeScriptCli.ts +++ b/src/project/nativeScriptCli.ts @@ -1,7 +1,6 @@ -import {spawn, execSync, ChildProcess} from 'child_process'; +import { ChildProcess, execSync, spawn } from 'child_process'; import { ILogger } from '../common/logger'; import * as utils from '../common/utilities'; -import * as os from 'os'; export class NativeScriptCli { private _path: string; @@ -17,7 +16,7 @@ export class NativeScriptCli { // always default to cmd on Windows // workaround for issue #121 https://github.com/NativeScript/nativescript-vscode-extension/issues/121 if (utils.getPlatform() === utils.Platform.Windows) { - this._shellPath = "cmd.exe"; + this._shellPath = 'cmd.exe'; } } @@ -25,31 +24,38 @@ export class NativeScriptCli { public executeGetVersion(): string { try { - return this.executeSync(["--version"], undefined); - } - catch(e) { + return this.executeSync(['--version'], undefined); + } catch (e) { this._logger.log(e); - throw new Error("NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting to explicitly set the absolute path to the NativeScript CLI."); + + const errorMessage = `NativeScript CLI not found. Use 'nativescript.tnsPath' workspace setting + to explicitly set the absolute path to the NativeScript CLI.`; + + throw new Error(errorMessage); } } public executeSync(args: string[], cwd: string): string { - args.unshift("--analyticsClient", "VSCode"); - let command: string = `${this._path} ${args.join(' ')}`; - this._logger.log(`[NativeScriptCli] execute: ${command}`,); + args.unshift('--analyticsClient', 'VSCode'); + const command: string = `${this._path} ${args.join(' ')}`; + + this._logger.log(`[NativeScriptCli] execute: ${command}`); - return execSync(command, { encoding: "utf8", cwd: cwd, shell: this._shellPath}).toString().trim(); + return execSync(command, { encoding: 'utf8', cwd, shell: this._shellPath}).toString().trim(); } public execute(args: string[], cwd: string): ChildProcess { - args.unshift("--analyticsClient", "VSCode"); - let command: string = `${this._path} ${args.join(' ')}`; + args.unshift('--analyticsClient', 'VSCode'); + const command: string = `${this._path} ${args.join(' ')}`; + this._logger.log(`[NativeScriptCli] execute: ${command}`); - let options = { cwd: cwd, shell: this._shellPath }; - let child: ChildProcess = spawn(this._path, args, options); + const options = { cwd, shell: this._shellPath }; + const child: ChildProcess = spawn(this._path, args, options); + child.stdout.setEncoding('utf8'); child.stderr.setEncoding('utf8'); + return child; } } diff --git a/src/project/project.ts b/src/project/project.ts index 3f64bc2..7e96475 100644 --- a/src/project/project.ts +++ b/src/project/project.ts @@ -1,10 +1,10 @@ -import {ChildProcess} from 'child_process'; -import {EventEmitter} from 'events'; -import {NativeScriptCli} from './nativeScriptCli'; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; import * as stream from 'stream'; +import { NativeScriptCli } from './nativeScriptCli'; import * as scanner from './streamScanner'; -export type DebugResult = { tnsProcess: ChildProcess, tnsOutputEventEmitter: EventEmitter }; +export interface IDebugResult { tnsProcess: ChildProcess; tnsOutputEventEmitter: EventEmitter; } export abstract class Project { private _appRoot: string; @@ -25,21 +25,21 @@ export abstract class Project { return this.executeRunCommand(tnsArgs); } - public abstract attach(tnsArgs?: string[]): DebugResult; + public abstract attach(tnsArgs?: string[]): IDebugResult; - public abstract debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): DebugResult; + public abstract debug(options: { stopOnEntry: boolean, watch: boolean }, tnsArgs?: string[]): IDebugResult; protected configureReadyEvent(readableStream: stream.Readable, eventEmitter: EventEmitter): void { - new scanner.StringMatchingScanner(readableStream).onEveryMatch("TypeScript compiler failed", () => { + new scanner.StringMatchingScanner(readableStream).onEveryMatch('TypeScript compiler failed', () => { eventEmitter.emit('tsCompilationError'); }); } protected executeRunCommand(args: string[]): ChildProcess { - return this.cli.execute(["run", this.platformName()].concat(args), this._appRoot); + return this._cli.execute(['run', this.platformName()].concat(args), this._appRoot); } protected executeDebugCommand(args: string[]): ChildProcess { - return this.cli.execute(["debug", this.platformName()].concat(args), this._appRoot); + return this._cli.execute(['debug', this.platformName()].concat(args), this._appRoot); } } diff --git a/src/project/streamScanner.ts b/src/project/streamScanner.ts index 3cc167d..ae64080 100644 --- a/src/project/streamScanner.ts +++ b/src/project/streamScanner.ts @@ -1,17 +1,17 @@ -import * as stream from 'stream'; +import { Readable } from 'stream'; export class StreamScanner { - private _stream: stream.Readable; - private _scanCallback: (data: string, stop: () => void) => void; + private _stream: Readable; + private _scanCallback: (data: string, stop: () => void) => void; - constructor(stream: stream.Readable, scanCallback: (data: string, stop: () => void) => void) { + constructor(stream: Readable, scanCallback: (data: string, stop: () => void) => void) { this._stream = stream; this._scanCallback = scanCallback; - this._stream.on("data", this.scan.bind(this)); - } + this._stream.on('data', this.scan.bind(this)); + } public stop() { - this._stream.removeListener("data", this.scan); + this._stream.removeListener('data', this.scan); } private scan(data: string | Buffer): void { @@ -19,74 +19,80 @@ export class StreamScanner { } } -export type MatchFound = { - chunk: string, - matches: RegExpMatchArray | number[] -}; +export interface IMatchFound { + chunk: string; + matches: RegExpMatchArray | number[]; +} -type MatchMeta = { - promise: Promise, - resolve: (match: MatchFound) => void, - reject: (err: Error) => void, - test: string | RegExp -}; +interface IMatchMeta { + promise: Promise; + resolve: (match: IMatchFound) => void; + reject: (err: Error) => void; + test: string | RegExp; +} export class StringMatchingScanner extends StreamScanner { - private _metas: MatchMeta[]; + private metas: IMatchMeta[]; - constructor(stream: stream.Readable) { + constructor(stream: Readable) { super(stream, (data: string, stop: () => void) => { - this._metas.forEach((meta, metaIndex) => { + this.metas.forEach((meta, metaIndex) => { if (meta.test instanceof RegExp) { - let result: RegExpMatchArray = data.match(meta.test); + const result: RegExpMatchArray = data.match(meta.test as RegExp); + if (result && result.length > 0) { this.matchFound(metaIndex, { chunk: data, matches: result }); } - } - else if (typeof meta.test === 'string') { - let result: number[] = []; // matches indices - let dataIndex = -1; - while((dataIndex = data.indexOf(meta.test, dataIndex + 1)) > -1) { + } else if (typeof meta.test === 'string') { + const result: number[] = []; // matches indices + let dataIndex = data.indexOf(meta.test as string, 0); + + while (dataIndex > -1) { result.push(dataIndex); + dataIndex = data.indexOf(meta.test as string, dataIndex + 1); } if (result.length > 0) { this.matchFound(metaIndex, { chunk: data, matches: result }); } - } - else { - throw new TypeError("Invalid type"); + } else { + throw new TypeError('Invalid type'); } }); }); - this._metas = []; + + this.metas = []; } - public onEveryMatch(test: string | RegExp, handler: (result: MatchFound) => void) { - let handlerWrapper = (result: MatchFound) => { + public onEveryMatch(test: string | RegExp, handler: (result: IMatchFound) => void) { + const handlerWrapper = (result: IMatchFound) => { handler(result); this.nextMatch(test).then(handlerWrapper); }; + this.nextMatch(test).then(handlerWrapper); } - public nextMatch(test: string | RegExp): Promise { - let meta: MatchMeta = { - test: test, - resolve: null, + public nextMatch(test: string | RegExp): Promise { + const meta: IMatchMeta = { + promise: null, reject: null, - promise: null + resolve: null, + test, }; - meta.promise = new Promise((resolve, reject) => { + + meta.promise = new Promise((resolve, reject) => { meta.resolve = resolve; meta.reject = reject; }); - this._metas.push(meta); + this.metas.push(meta); + return meta.promise; } - private matchFound(matchMetaIndex: number, matchResult: MatchFound) { - let meta: MatchMeta = this._metas[matchMetaIndex]; - this._metas.splice(matchMetaIndex, 1); // remove the meta + private matchFound(matchMetaIndex: number, matchResult: IMatchFound) { + const meta: IMatchMeta = this.metas[matchMetaIndex]; + + this.metas.splice(matchMetaIndex, 1); // remove the meta meta.resolve(matchResult); } } diff --git a/src/services/channelLogger.ts b/src/services/channelLogger.ts index 676fd76..e72ae65 100644 --- a/src/services/channelLogger.ts +++ b/src/services/channelLogger.ts @@ -1,5 +1,5 @@ -import { ILogger, LogLevel } from '../common/logger'; import { OutputChannel } from 'vscode'; +import { ILogger, LogLevel } from '../common/logger'; export class ChannelLogger implements ILogger { private minLogLevel: LogLevel = LogLevel.Log; @@ -9,9 +9,9 @@ export class ChannelLogger implements ILogger { this.channel = channel; } - log(msg: string, level: LogLevel = LogLevel.Log): void { + public log(msg: string, level: LogLevel = LogLevel.Log): void { if (level >= this.minLogLevel) { - this.channel.appendLine(msg); - } + this.channel.appendLine(msg); + } } -} \ No newline at end of file +} diff --git a/src/services/extensionHostServices.ts b/src/services/extensionHostServices.ts index 62dc982..b8210d8 100644 --- a/src/services/extensionHostServices.ts +++ b/src/services/extensionHostServices.ts @@ -1,37 +1,40 @@ import * as vscode from 'vscode'; -import {Services as BaseServices} from './services'; -import {iOSTeamService} from './iOSTeamService'; -import {AnalyticsService} from '../analytics/analyticsService'; -import {WorkspaceConfigService} from '../common/workspaceConfigService'; +import { AnalyticsService } from '../analytics/analyticsService'; +import { WorkspaceConfigService } from '../common/workspaceConfigService'; +import { iOSTeamService } from './iOSTeamService'; +import { Services as BaseServices } from './services'; export class ExtensionHostServices extends BaseServices { + public cliVersion: string; + public extensionVersion: string; + private _globalState: vscode.Memento; private _workspaceConfigService: WorkspaceConfigService; private _iOSTeamService: iOSTeamService; private _analyticsService: AnalyticsService; - public cliVersion: string; - public extensionVersion: string; - public get globalState(): vscode.Memento { return this._globalState; } public set globalState(globalState: vscode.Memento) { this._globalState = globalState; } public get workspaceConfigService(): WorkspaceConfigService { this._workspaceConfigService = this._workspaceConfigService || new WorkspaceConfigService(); + return this._workspaceConfigService; } public get iOSTeamService(): iOSTeamService { this._iOSTeamService = this._iOSTeamService || new iOSTeamService(); + return this._iOSTeamService; } public get analyticsService(): AnalyticsService { - this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState, this.cliVersion, this.extensionVersion); + this._analyticsService = this._analyticsService || new AnalyticsService(this.globalState, this.cliVersion, this.extensionVersion, this._logger); + return this._analyticsService; } } -export let Services = new ExtensionHostServices(); \ No newline at end of file +export let services = new ExtensionHostServices(); diff --git a/src/services/iOSTeamService.ts b/src/services/iOSTeamService.ts index 57454ba..6996c37 100644 --- a/src/services/iOSTeamService.ts +++ b/src/services/iOSTeamService.ts @@ -1,39 +1,42 @@ -import * as path from 'path'; import * as fs from 'fs'; +import * as path from 'path'; import * as vscode from 'vscode'; -import {QuickPickItem} from 'vscode'; +// tslint:disable-next-line:class-name export class iOSTeamService { public selectTeam(): Promise<{ id: string, name: string }> { return new Promise((resolve, reject) => { - const workspaceTeamId = vscode.workspace.getConfiguration().get("nativescript.iosTeamId"); + const workspaceTeamId = vscode.workspace.getConfiguration().get('nativescript.iosTeamId'); if (workspaceTeamId) { resolve({ id: workspaceTeamId, - name: undefined // irrelevant + name: undefined, // irrelevant }); + return; } - let developmentTeams = this.getDevelopmentTeams(); + const developmentTeams = this.getDevelopmentTeams(); + if (developmentTeams.length > 1) { - let quickPickItems: Array = developmentTeams.map((team) => { + const quickPickItems: vscode.QuickPickItem[] = developmentTeams.map((team) => { return { + description: team.id, label: team.name, - description: team.id }; }); + vscode.window.showQuickPick( quickPickItems, { - placeHolder: "Select your development team" + placeHolder: 'Select your development team', }) - .then((val: QuickPickItem) => { - vscode.workspace.getConfiguration().update("nativescript.iosTeamId", val.description); + .then((val: vscode.QuickPickItem) => { + vscode.workspace.getConfiguration().update('nativescript.iosTeamId', val.description); resolve({ id: val.description, - name: val.label - }) + name: val.label, + }); }); } else { resolve(); @@ -43,21 +46,24 @@ export class iOSTeamService { private getDevelopmentTeams(): Array<{ id: string, name: string }> { try { - let dir = path.join(process.env.HOME, "Library/MobileDevice/Provisioning Profiles/"); - let files = fs.readdirSync(dir); - let teamIds: any = {}; - for (let file of files) { - let filePath = path.join(dir, file); - let data = fs.readFileSync(filePath, {encoding: "utf8"}); - let teamId = this.getProvisioningProfileValue("TeamIdentifier", data); - let teamName = this.getProvisioningProfileValue("TeamName", data); + const dir = path.join(process.env.HOME, 'Library/MobileDevice/Provisioning Profiles/'); + const files = fs.readdirSync(dir); + const teamIds: any = {}; + + for (const file of files) { + const filePath = path.join(dir, file); + const data = fs.readFileSync(filePath, {encoding: 'utf8'}); + const teamId = this.getProvisioningProfileValue('TeamIdentifier', data); + const teamName = this.getProvisioningProfileValue('TeamName', data); + if (teamId) { teamIds[teamId] = teamName; } } - let teamIdsArray = new Array<{ id: string, name: string }>(); - for (let teamId in teamIds) { + const teamIdsArray = new Array<{ id: string, name: string }>(); + + for (const teamId in teamIds) { teamIdsArray.push({id: teamId, name: teamIds[teamId]}); } @@ -69,17 +75,20 @@ export class iOSTeamService { } private getProvisioningProfileValue(name: string, text: string): string { - let findStr = "" + name + ""; + const findStr = '' + name + ''; let index = text.indexOf(findStr); + if (index > 0) { - index = text.indexOf("", index + findStr.length); + index = text.indexOf('', index + findStr.length); if (index > 0) { - index += "".length; - let endIndex = text.indexOf("", index); - let result = text.substring(index, endIndex); + index += ''.length; + const endIndex = text.indexOf('', index); + const result = text.substring(index, endIndex); + return result; } } + return null; } -} \ No newline at end of file +} diff --git a/src/services/services.ts b/src/services/services.ts index 0524451..417c3bd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1,5 +1,5 @@ -import {ILogger} from '../common/logger'; -import {NativeScriptCli} from '../project/nativeScriptCli'; +import { ILogger } from '../common/logger'; +import { NativeScriptCli } from '../project/nativeScriptCli'; export class Services { protected _cliPath: string; @@ -17,6 +17,7 @@ export class Services { public cli(): NativeScriptCli { this._cli = this._cli || new NativeScriptCli(this._cliPath, this.logger); + return this._cli; } } diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000..f789e25 --- /dev/null +++ b/tslint.json @@ -0,0 +1,35 @@ +{ + "extends": [ + "tslint-eslint-rules", + "tslint:latest" + ], + "rules": { + "quotemark": [true, + "single", + "avoid-escape" + ], + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-module", + "check-separator", + "check-type" + ], + "newline-before-return": true, + "no-unused-variable": true, + "no-duplicate-variable": true, + "no-unused-expression": [true, "allow-fast-null-checks"], + "ter-newline-after-var": true, + "max-line-length": [ true, 160 ], + "max-classes-per-file": false, + "variable-name": [ true, "allow-leading-underscore" ], + "forin": false, + "no-implicit-dependencies": [true, "dev"] + }, + "linterOptions": { + "exclude": [ + "**/tests/**" + ] + } +} \ No newline at end of file From c60c748ed7ebbde8d375eb82d41c0dbaba04c67c Mon Sep 17 00:00:00 2001 From: ivanovit Date: Fri, 29 Jun 2018 10:19:13 +0300 Subject: [PATCH 05/12] Add tests The deleted tests were focused on debug protocol. Now these scenarios should be covered by the chrome-debug-adapter-core. Currently, the new tests cover only NS related code. --- .vscode/launch.json | 77 +-- package.json | 10 +- src/debug-adapter/nativeScriptDebug.ts | 7 +- src/debug-adapter/nativeScriptDebugAdapter.ts | 286 ++++++------ src/tests/.vscode/launch.json | 53 --- src/tests/.vscode/tasks.json | 10 - src/tests/adapter.test.ts | 439 ------------------ src/tests/config/mac.json | 6 - src/tests/config/mocha.opts | 5 +- src/tests/config/win.json | 6 - src/tests/nativeScriptDebugAdapter.tests.ts | 204 ++++++++ .../nativeScriptPathTransformer.tests.ts | 34 ++ .../nativeScriptTargetDiscovery.tests.ts | 45 ++ src/tests/nsDebugClient.ts | 67 --- src/tests/pathTransformData.ts | 19 + src/tests/scenario.ts | 126 ----- .../App_Resources/Android/AndroidManifest.xml | 40 -- .../app/App_Resources/Android/app.gradle | 15 - .../Android/drawable-hdpi/icon.png | Bin 10946 -> 0 bytes .../Android/drawable-ldpi/icon.png | Bin 6170 -> 0 bytes .../Android/drawable-mdpi/icon.png | Bin 7594 -> 0 bytes .../Android/drawable-nodpi/splashscreen.9.png | Bin 26614 -> 0 bytes .../app/App_Resources/iOS/Info.plist | 66 --- .../testdata/DebuggerStatement/app/app.js | 2 - .../app/debuggerStatement.js | 5 - .../DebuggerStatement/app/main-page.js | 6 - .../DebuggerStatement/app/main-page.xml | 7 - .../DebuggerStatement/app/package.json | 3 - .../testdata/DebuggerStatement/package.json | 20 - .../App_Resources/Android/AndroidManifest.xml | 40 -- .../app/App_Resources/Android/app.gradle | 15 - .../Android/drawable-hdpi/icon.png | Bin 10946 -> 0 bytes .../Android/drawable-ldpi/icon.png | Bin 6170 -> 0 bytes .../Android/drawable-mdpi/icon.png | Bin 7594 -> 0 bytes .../Android/drawable-nodpi/splashscreen.9.png | Bin 26614 -> 0 bytes .../TestApp1/app/App_Resources/iOS/Info.plist | 66 --- src/tests/testdata/TestApp1/app/app.js | 2 - .../TestApp1/app/conditionalBreakpoint.js | 2 - src/tests/testdata/TestApp1/app/consoleLog.js | 3 - .../TestApp1/app/file with space in name.js | 5 - src/tests/testdata/TestApp1/app/main-page.js | 8 - src/tests/testdata/TestApp1/app/main-page.xml | 7 - src/tests/testdata/TestApp1/app/package.json | 3 - src/tests/testdata/TestApp1/package.json | 20 - .../testdata/TestApp2/.vscode/launch.json | 86 ---- .../App_Resources/Android/AndroidManifest.xml | 40 -- .../app/App_Resources/Android/app.gradle | 15 - .../Android/drawable-hdpi/icon.png | Bin 10946 -> 0 bytes .../Android/drawable-ldpi/icon.png | Bin 6170 -> 0 bytes .../Android/drawable-mdpi/icon.png | Bin 7594 -> 0 bytes .../Android/drawable-nodpi/splashscreen.9.png | Bin 26614 -> 0 bytes .../TestApp2/app/App_Resources/iOS/Info.plist | 66 --- src/tests/testdata/TestApp2/app/app.js | 10 - src/tests/testdata/TestApp2/app/package.json | 3 - src/tests/testdata/TestApp2/package.json | 20 - src/tests/testsContext.ts | 89 ---- src/tsconfig.json | 9 +- tslint.json | 8 +- 58 files changed, 484 insertions(+), 1591 deletions(-) delete mode 100644 src/tests/.vscode/launch.json delete mode 100644 src/tests/.vscode/tasks.json delete mode 100644 src/tests/adapter.test.ts delete mode 100644 src/tests/config/mac.json delete mode 100644 src/tests/config/win.json create mode 100644 src/tests/nativeScriptDebugAdapter.tests.ts create mode 100644 src/tests/nativeScriptPathTransformer.tests.ts create mode 100644 src/tests/nativeScriptTargetDiscovery.tests.ts delete mode 100644 src/tests/nsDebugClient.ts create mode 100644 src/tests/pathTransformData.ts delete mode 100644 src/tests/scenario.ts delete mode 100644 src/tests/testdata/DebuggerStatement/app/App_Resources/Android/AndroidManifest.xml delete mode 100644 src/tests/testdata/DebuggerStatement/app/App_Resources/Android/app.gradle delete mode 100755 src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-hdpi/icon.png delete mode 100755 src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-ldpi/icon.png delete mode 100755 src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-mdpi/icon.png delete mode 100644 src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png delete mode 100644 src/tests/testdata/DebuggerStatement/app/App_Resources/iOS/Info.plist delete mode 100644 src/tests/testdata/DebuggerStatement/app/app.js delete mode 100644 src/tests/testdata/DebuggerStatement/app/debuggerStatement.js delete mode 100644 src/tests/testdata/DebuggerStatement/app/main-page.js delete mode 100644 src/tests/testdata/DebuggerStatement/app/main-page.xml delete mode 100644 src/tests/testdata/DebuggerStatement/app/package.json delete mode 100644 src/tests/testdata/DebuggerStatement/package.json delete mode 100644 src/tests/testdata/TestApp1/app/App_Resources/Android/AndroidManifest.xml delete mode 100644 src/tests/testdata/TestApp1/app/App_Resources/Android/app.gradle delete mode 100755 src/tests/testdata/TestApp1/app/App_Resources/Android/drawable-hdpi/icon.png delete mode 100755 src/tests/testdata/TestApp1/app/App_Resources/Android/drawable-ldpi/icon.png delete mode 100755 src/tests/testdata/TestApp1/app/App_Resources/Android/drawable-mdpi/icon.png delete mode 100644 src/tests/testdata/TestApp1/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png delete mode 100644 src/tests/testdata/TestApp1/app/App_Resources/iOS/Info.plist delete mode 100644 src/tests/testdata/TestApp1/app/app.js delete mode 100644 src/tests/testdata/TestApp1/app/conditionalBreakpoint.js delete mode 100644 src/tests/testdata/TestApp1/app/consoleLog.js delete mode 100644 src/tests/testdata/TestApp1/app/file with space in name.js delete mode 100644 src/tests/testdata/TestApp1/app/main-page.js delete mode 100644 src/tests/testdata/TestApp1/app/main-page.xml delete mode 100644 src/tests/testdata/TestApp1/app/package.json delete mode 100644 src/tests/testdata/TestApp1/package.json delete mode 100644 src/tests/testdata/TestApp2/.vscode/launch.json delete mode 100644 src/tests/testdata/TestApp2/app/App_Resources/Android/AndroidManifest.xml delete mode 100644 src/tests/testdata/TestApp2/app/App_Resources/Android/app.gradle delete mode 100755 src/tests/testdata/TestApp2/app/App_Resources/Android/drawable-hdpi/icon.png delete mode 100755 src/tests/testdata/TestApp2/app/App_Resources/Android/drawable-ldpi/icon.png delete mode 100755 src/tests/testdata/TestApp2/app/App_Resources/Android/drawable-mdpi/icon.png delete mode 100644 src/tests/testdata/TestApp2/app/App_Resources/Android/drawable-nodpi/splashscreen.9.png delete mode 100644 src/tests/testdata/TestApp2/app/App_Resources/iOS/Info.plist delete mode 100644 src/tests/testdata/TestApp2/app/app.js delete mode 100644 src/tests/testdata/TestApp2/app/package.json delete mode 100644 src/tests/testdata/TestApp2/package.json delete mode 100644 src/tests/testsContext.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index ae25e4f..2da8ae4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,11 +2,11 @@ "version": "0.1.0", "configurations": [ { - "name": "launch as server", + "name": "Launch as server", "type": "node", "request": "launch", "program": "${workspaceRoot}/out/debug-adapter/nativeScriptDebug.js", - "runtimeArgs": ["--nolazy"], + "runtimeArgs": ["--nolazy"], "args": [ "--server=4712" ], "stopOnEntry": false, "sourceMaps": true, @@ -14,7 +14,7 @@ "cwd": "${workspaceFolder}" }, { - "name": "launch in extension host", + "name": "Launch in extension host", "type": "extensionHost", "request": "launch", // Path to VSCode executablensDebugClient @@ -24,61 +24,22 @@ ], "stopOnEntry": false, "sourceMaps": true, - "outFiles": [ "${workspaceFolder}/out/**/*.js" ], + "outFiles": [ "${workspaceFolder}/out/**/*.js" ] }, - { - "name": "run tests on mac", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/src/tests/config/mocha.opts", - "--config", "${workspaceRoot}/src/tests/config/mac.json", - "${workspaceRoot}/out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out", - "cwd": "${workspaceRoot}" - }, - { - "name": "run tests on win", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/src/tests/config/mocha.opts", - "--config", "${workspaceRoot}/src/tests/config/win.json", - "${workspaceRoot}/out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out", - "cwd": "${workspaceRoot}" - }, - { - "name": "run tests (custom)", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/src/tests/config/mocha.opts", - "--config", "${workspaceRoot}/src/tests/config/custom.json", - "${workspaceRoot}/out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out", - "cwd": "${workspaceRoot}" - } + { + "name": "Run tests", + "type": "node", + "request": "launch", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", "test", "--" + ] + } ], - "compounds": [ - { - "name": "Extension + Server", - "configurations": [ "launch in extension host", "launch as server" ] - } - ] + "compounds": [ + { + "name": "Extension + Server", + "configurations": [ "Launch in extension host", "Launch as server" ] + } + ] } \ No newline at end of file diff --git a/package.json b/package.json index 5ff3730..40dca2c 100644 --- a/package.json +++ b/package.json @@ -34,15 +34,15 @@ }, "devDependencies": { "@types/lodash": "^4.14.109", - "@types/mocha": "2.2.41", + "@types/mocha": "^5.2.1", "@types/node": "6.0.46", - "mocha": "2.5.3", + "mocha": "^5.2.0", + "sinon": "^5.0.10", "tslint": "5.10.0", "tslint-eslint-rules": "^5.3.1", "typescript": "2.6.2", "vsce": "~1.36.0", "vscode": "~1.1.10", - "vscode-debugadapter-testsupport": "1.26.0", "vscode-debugprotocol": "^1.28.0-pre.1" }, "scripts": { @@ -52,9 +52,7 @@ "package": "vsce package", "full-build": "npm run clean && npm install && npm run build && npm run package", "launch-as-server": "node --nolazy ./out/debug-adapter/nativeScriptDebug.js --server=4712", - "test-mac": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/mac.json ./out/tests", - "test-win": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/win.json ./out/tests", - "test-custom": "mocha --opts ./src/tests/config/mocha.opts --config ../../src/tests/config/custom.json ./out/tests", + "test": "mocha --opts ./src/tests/config/mocha.opts", "tslint": "tslint -p ./src/tsconfig.json -c tslint.json" }, "main": "./out/main", diff --git a/src/debug-adapter/nativeScriptDebug.ts b/src/debug-adapter/nativeScriptDebug.ts index 20e02fe..c229d87 100644 --- a/src/debug-adapter/nativeScriptDebug.ts +++ b/src/debug-adapter/nativeScriptDebug.ts @@ -1,7 +1,10 @@ import * as os from 'os'; import * as path from 'path'; import { chromeConnection, ChromeDebugSession } from 'vscode-chrome-debug-core'; -import { NativeScriptDebugAdapter } from './nativeScriptDebugAdapter'; +import { AndroidProject } from '../project/androidProject'; +import { IosProject } from '../project/iosProject'; +import { NativeScriptCli } from '../project/nativeScriptCli'; +import { nativeScriptDebugAdapterGenerator } from './nativeScriptDebugAdapter'; import { NativeScriptPathTransformer } from './nativeScriptPathTransformer'; import { NativeScriptTargetDiscovery } from './nativeScriptTargetDiscovery'; @@ -13,7 +16,7 @@ class NSAndroidConnection extends chromeConnection.ChromeConnection { ChromeDebugSession.run(ChromeDebugSession.getSession( { - adapter: NativeScriptDebugAdapter, + adapter: nativeScriptDebugAdapterGenerator(IosProject, AndroidProject, NativeScriptCli), chromeConnection: NSAndroidConnection, extensionName: 'nativescript-extension', logFilePath: path.join(os.tmpdir(), 'nativescript-extension.txt'), diff --git a/src/debug-adapter/nativeScriptDebugAdapter.ts b/src/debug-adapter/nativeScriptDebugAdapter.ts index e96b1b9..6cc1196 100644 --- a/src/debug-adapter/nativeScriptDebugAdapter.ts +++ b/src/debug-adapter/nativeScriptDebugAdapter.ts @@ -10,196 +10,200 @@ import { IosProject } from '../project/iosProject'; import { NativeScriptCli } from '../project/nativeScriptCli'; import { IDebugResult } from '../project/project'; -export class NativeScriptDebugAdapter extends ChromeDebugAdapter { - private _tnsProcess: ChildProcess; - private _idCounter = 0; - private _pendingRequests: object = {}; - - public attach(args: any): Promise { - return this.processRequestAndAttach(args); - } - - public launch(args: any): Promise { - return this.processRequestAndAttach(args); - } - - public disconnect(args: any): void { - super.disconnect(args); - - if (this._tnsProcess) { - this._tnsProcess.stdout.removeAllListeners(); - this._tnsProcess.stderr.removeAllListeners(); - this._tnsProcess.removeAllListeners(); - utils.killProcess(this._tnsProcess); +export function nativeScriptDebugAdapterGenerator(iosProject: typeof IosProject, + androidProject: typeof AndroidProject, + nativeScriptCli: typeof NativeScriptCli) { + return class NativeScriptDebugAdapter extends ChromeDebugAdapter { + private _tnsProcess: ChildProcess; + private _idCounter = 0; + private _pendingRequests: object = {}; + + public attach(args: any): Promise { + return this.processRequestAndAttach(args); } - } - public onExtensionResponse(response) { - this._pendingRequests[response.requestId](response.result); - delete this._pendingRequests[response.requestId]; - } + public launch(args: any): Promise { + return this.processRequestAndAttach(args); + } + + public disconnect(args: any): void { + super.disconnect(args); + + if (this._tnsProcess) { + this._tnsProcess.stdout.removeAllListeners(); + this._tnsProcess.stderr.removeAllListeners(); + this._tnsProcess.removeAllListeners(); + utils.killProcess(this._tnsProcess); + } + } + + public onExtensionResponse(response) { + this._pendingRequests[response.requestId](response.result); + delete this._pendingRequests[response.requestId]; + } - private async processRequestAndAttach(args: any) { - const transformedArgs = await this.processRequest(args); + private async processRequestAndAttach(args: any) { + const transformedArgs = await this.processRequest(args); - (this.pathTransformer as any).setTargetPlatform(args.platform); - (ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000; + (this.pathTransformer as any).setTargetPlatform(args.platform); + (ChromeDebugAdapter as any).SET_BREAKPOINTS_TIMEOUT = 20000; - return super.attach(transformedArgs); - } + return super.attach(transformedArgs); + } - private async processRequest(args: any): Promise { - args = this.translateArgs(args); + private async processRequest(args: any): Promise { + args = this.translateArgs(args); - this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START)); + this._session.sendEvent(new Event(extProtocol.BEFORE_DEBUG_START)); - const tnsPath = await this.callRemoteMethod('workspaceConfigService', 'tnsPath'); - const cli = new NativeScriptCli(tnsPath, logger); + const tnsPath = await this.callRemoteMethod('workspaceConfigService', 'tnsPath'); + const cli = new nativeScriptCli(tnsPath, logger); - const project = args.platform === 'ios' ? - new IosProject(args.appRoot, cli) : - new AndroidProject(args.appRoot, cli); + const project = args.platform === 'ios' ? + new iosProject(args.appRoot, cli) : + new androidProject(args.appRoot, cli); - this.callRemoteMethod('analyticsService', 'launchDebugger', args.request, args.platform); + this.callRemoteMethod('analyticsService', 'launchDebugger', args.request, args.platform); - // Run CLI Command - const version = project.cli.executeGetVersion(); + // Run CLI Command + const version = project.cli.executeGetVersion(); - this.log(`[NSDebugAdapter] Using tns CLI v${version} on path '${project.cli.path}'\n`); - this.log('[NSDebugAdapter] Running tns command...\n'); - let cliCommand: IDebugResult; + this.log(`[NSDebugAdapter] Using tns CLI v${version} on path '${project.cli.path}'\n`); + this.log('[NSDebugAdapter] Running tns command...\n'); + let cliCommand: IDebugResult; - if (args.request === 'launch') { - let tnsArgs = args.tnsArgs; + if (args.request === 'launch') { + let tnsArgs = args.tnsArgs; - // For iOS the TeamID is required if there's more than one. - // Therefore if not set, show selection to the user. - if (args.platform && args.platform.toLowerCase() === 'ios') { - const teamId = this.getTeamId(path.join(args.appRoot, 'app'), tnsArgs); + // For iOS the TeamID is required if there's more than one. + // Therefore if not set, show selection to the user. + if (args.platform && args.platform.toLowerCase() === 'ios') { + const teamId = this.getTeamId(path.join(args.appRoot, 'app'), tnsArgs); - if (!teamId) { - const selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam'); + if (!teamId) { + const selectedTeam = await this.callRemoteMethod<{ id: string, name: string }>('iOSTeamService', 'selectTeam'); - if (selectedTeam) { - // add the selected by the user Team Id - tnsArgs = (tnsArgs || []).concat(['--teamId', selectedTeam.id]); - this.log(`[NSDebugAdapter] Using iOS Team ID '${selectedTeam.id}', you can change this in the workspace settings.\n`); + if (selectedTeam) { + // add the selected by the user Team Id + tnsArgs = (tnsArgs || []).concat(['--teamId', selectedTeam.id]); + this.log(`[NSDebugAdapter] Using iOS Team ID '${selectedTeam.id}', you can change this in the workspace settings.\n`); + } } } + + cliCommand = project.debug({ stopOnEntry: args.stopOnEntry, watch: args.watch }, tnsArgs); + } else if (args.request === 'attach') { + cliCommand = project.attach(args.tnsArgs); } - cliCommand = project.debug({ stopOnEntry: args.stopOnEntry, watch: args.watch }, tnsArgs); - } else if (args.request === 'attach') { - cliCommand = project.attach(args.tnsArgs); - } + if (cliCommand.tnsProcess) { + this._tnsProcess = cliCommand.tnsProcess; + cliCommand.tnsProcess.stdout.on('data', (data) => { this.log(data.toString()); }); + cliCommand.tnsProcess.stderr.on('data', (data) => { this.log(data.toString()); }); + + cliCommand.tnsProcess.on('close', (code, signal) => { + this.log(`[NSDebugAdapter] The tns command finished its execution with code ${code}.\n`); + + // Sometimes we execute "tns debug android --start" and the process finishes + // which is totally fine. If there's an error we need to Terminate the session. + if (code !== 0) { + this.log(`The tns command finished its execution with code ${code}`); + this._session.sendEvent(new TerminatedEvent()); + } + }); + } - if (cliCommand.tnsProcess) { - this._tnsProcess = cliCommand.tnsProcess; - cliCommand.tnsProcess.stdout.on('data', (data) => { this.log(data.toString()); }); - cliCommand.tnsProcess.stderr.on('data', (data) => { this.log(data.toString()); }); + this.log('[NSDebugAdapter] Watching the tns CLI output to receive a connection token\n'); - cliCommand.tnsProcess.on('close', (code, signal) => { - this.log(`[NSDebugAdapter] The tns command finished its execution with code ${code}.\n`); + return new Promise ((res, rej) => { + cliCommand.tnsOutputEventEmitter.on('readyForConnection', (connectionToken: string | number) => { + this.log(`[NSDebugAdapter] Ready to attach to application on ${connectionToken}\n`); + args.port = connectionToken; - // Sometimes we execute "tns debug android --start" and the process finishes - // which is totally fine. If there's an error we need to Terminate the session. - if (code !== 0) { - this.log(`The tns command finished its execution with code ${code}`); - this._session.sendEvent(new TerminatedEvent()); - } + res(args); + }); }); } - this.log('[NSDebugAdapter] Watching the tns CLI output to receive a connection token\n'); - - return new Promise ((res, rej) => { - cliCommand.tnsOutputEventEmitter.on('readyForConnection', (connectionToken: string | number) => { - this.log(`[NSDebugAdapter] Ready to attach to application on ${connectionToken}\n`); - args.port = connectionToken; + private translateArgs(args): any { + if (args.diagnosticLogging) { + args.trace = args.diagnosticLogging; + } - res(args); - }); - }); - } + if (args.appRoot) { + args.webRoot = args.appRoot; + } - private translateArgs(args): any { - if (args.diagnosticLogging) { - args.trace = args.diagnosticLogging; + return args; } - if (args.appRoot) { - args.webRoot = args.appRoot; + private log(text: string): void { + this._session.sendEvent(new OutputEvent(text)); } - return args; - } + private getTeamId(appRoot: string, tnsArgs?: string[]): string { + // try to get the TeamId from the TnsArgs + if (tnsArgs) { + const teamIdArgIndex = tnsArgs.indexOf('--teamId'); - private log(text: string): void { - this._session.sendEvent(new OutputEvent(text)); - } + if (teamIdArgIndex > 0 && teamIdArgIndex + 1 < tnsArgs.length) { + return tnsArgs[ teamIdArgIndex + 1 ]; + } + } - private getTeamId(appRoot: string, tnsArgs?: string[]): string { - // try to get the TeamId from the TnsArgs - if (tnsArgs) { - const teamIdArgIndex = tnsArgs.indexOf('--teamId'); + // try to get the TeamId from the buildxcconfig or teamid file + const teamIdFromConfig = this.readTeamId(appRoot); - if (teamIdArgIndex > 0 && teamIdArgIndex + 1 < tnsArgs.length) { - return tnsArgs[ teamIdArgIndex + 1 ]; + if (teamIdFromConfig) { + return teamIdFromConfig; } - } - // try to get the TeamId from the buildxcconfig or teamid file - const teamIdFromConfig = this.readTeamId(appRoot); - - if (teamIdFromConfig) { - return teamIdFromConfig; + // we should get the Teams from the machine and ask the user if they are more than 1 + return null; } - // we should get the Teams from the machine and ask the user if they are more than 1 - return null; - } - - private readXCConfig(appRoot: string, flag: string): string { - const xcconfigFile = path.join(appRoot, 'App_Resources/iOS/build.xcconfig'); + private readXCConfig(appRoot: string, flag: string): string { + const xcconfigFile = path.join(appRoot, 'App_Resources/iOS/build.xcconfig'); - if (fs.existsSync(xcconfigFile)) { - const text = fs.readFileSync(xcconfigFile, { encoding: 'utf8'}); - let teamId: string; + if (fs.existsSync(xcconfigFile)) { + const text = fs.readFileSync(xcconfigFile, { encoding: 'utf8'}); + let teamId: string; - text.split(/\r?\n/).forEach((line) => { - line = line.replace(/\/(\/)[^\n]*$/, ''); - if (line.indexOf(flag) >= 0) { - teamId = line.split('=')[1].trim(); - if (teamId[teamId.length - 1] === ';') { - teamId = teamId.slice(0, -1); + text.split(/\r?\n/).forEach((line) => { + line = line.replace(/\/(\/)[^\n]*$/, ''); + if (line.indexOf(flag) >= 0) { + teamId = line.split('=')[1].trim(); + if (teamId[teamId.length - 1] === ';') { + teamId = teamId.slice(0, -1); + } } + }); + if (teamId) { + return teamId; } - }); - if (teamId) { - return teamId; } - } - const fileName = path.join(appRoot, 'teamid'); + const fileName = path.join(appRoot, 'teamid'); - if (fs.existsSync(fileName)) { - return fs.readFileSync(fileName, { encoding: 'utf8' }); - } + if (fs.existsSync(fileName)) { + return fs.readFileSync(fileName, { encoding: 'utf8' }); + } - return null; - } + return null; + } - private readTeamId(appRoot): string { - return this.readXCConfig(appRoot, 'DEVELOPMENT_TEAM'); - } + private readTeamId(appRoot): string { + return this.readXCConfig(appRoot, 'DEVELOPMENT_TEAM'); + } - private callRemoteMethod(service: string, method: string, ...args: any[]): Promise { - const request: extProtocol.IRequest = { id: `req${++this._idCounter}`, service, method, args }; + private callRemoteMethod(service: string, method: string, ...args: any[]): Promise { + const request: extProtocol.IRequest = { id: `req${++this._idCounter}`, service, method, args }; - return new Promise((res, rej) => { - this._pendingRequests[request.id] = res; + return new Promise((res, rej) => { + this._pendingRequests[request.id] = res; - this._session.sendEvent(new Event(extProtocol.NS_DEBUG_ADAPTER_MESSAGE, request)); - }); - } + this._session.sendEvent(new Event(extProtocol.NS_DEBUG_ADAPTER_MESSAGE, request)); + }); + } + }; } diff --git a/src/tests/.vscode/launch.json b/src/tests/.vscode/launch.json deleted file mode 100644 index 9eb9aa5..0000000 --- a/src/tests/.vscode/launch.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "version": "0.1.0", - "configurations": [ - { - "name": "run tests on mac", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/config/mocha.opts", - "--config", "${workspaceRoot}/config/mac.json", - "${workspaceRoot}/../../out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/../../out", - "cwd": "${workspaceRoot}/../" - }, - { - "name": "run tests on win", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/config/mocha.opts", - "--config", "${workspaceRoot}/config/win.json", - "${workspaceRoot}/../../out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/../../out", - "cwd": "${workspaceRoot}/../" - }, - { - "name": "run tests (custom)", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", - "runtimeArgs": [ "--nolazy" ], - "args": [ - "--opts", "${workspaceRoot}/config/mocha.opts", - "--config", "${workspaceRoot}/config/custom.json", - "${workspaceRoot}/../../out/tests/" - ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceRoot}/../../out", - "cwd": "${workspaceRoot}/../" - } - ] -} \ No newline at end of file diff --git a/src/tests/.vscode/tasks.json b/src/tests/.vscode/tasks.json deleted file mode 100644 index 0d71c21..0000000 --- a/src/tests/.vscode/tasks.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "0.1.0", - "windows": { - "command": "..\\..\\node_modules\\.bin\\tsc" - }, - "command": "../../node_modules/.bin/tsc", - "isShellCommand": true, - "args": ["-p", "../"], - "problemMatcher": "$tsc" -} \ No newline at end of file diff --git a/src/tests/adapter.test.ts b/src/tests/adapter.test.ts deleted file mode 100644 index 9a4123d..0000000 --- a/src/tests/adapter.test.ts +++ /dev/null @@ -1,439 +0,0 @@ -import * as path from 'path'; -import * as assert from 'assert'; -import {DebugProtocol} from 'vscode-debugprotocol'; -import {NsDebugClient} from './nsDebugClient'; -import {Scenario} from './scenario'; -import {TestsConfig, TestsContext} from './testsContext'; - -describe('The adapter', () => { - - let dc: NsDebugClient; - let context: TestsContext = new TestsContext(); - let config: TestsConfig = context.getTestsConfig(); - - console.log(`Tests Configuration: ${JSON.stringify(config)}`); - - function waitFor(miliseconds) { - return new Promise(r => setTimeout(r, miliseconds)); - } - - function iosOrAndroid(platform, iosValue, androidValue) { - return platform == 'ios' ? iosValue : androidValue; - } - - before(function() { - if (!config.skipSuitePrepare) { - context.prepare(); - } - - dc = new NsDebugClient('node', context.getDebugAdapterMainPath(), 'nativescript'); - // dc.setTimeout(0); // No timeout. Useful when debugging the debug adapter. - }); - - beforeEach(function(done) { - dc.start(config.port || undefined).then(_ => done(), done); - }); - - afterEach(function(done) { - dc.removeAllListeners('stopped'); - dc.removeAllListeners('initialized'); - dc.stop().then(_ => waitFor(10000)).then(_ => done(), done); // Stop the DebugClient - }); - - it('should produce error on unknown request', done => { - dc.send('illegal_request').then(() => { - done(new Error('doesn\'t produce error error on unknown request')); - }, err => { done() }); - }); - - it('should return supported features', () => { - return dc.initializeRequest().then(response => { - assert.equal(response.body.supportsFunctionBreakpoints, false); - assert.equal(response.body.supportsConfigurationDoneRequest, true); - }); - }); - - it.skip('should produce error for invalid \'pathFormat\'', done => { - dc.initializeRequest({ - adapterID: 'mock', - linesStartAt1: true, - columnsStartAt1: true, - pathFormat: 'url' - }).then(response => { - done(new Error('does not report error on invalid \'pathFormat\' attribute')); - }).catch(err => { - done(); // error expected - }); - }); - - // Test cases are generated for all active platforms - config.platforms.forEach(platform => { - let meta = `on ${platform} ${config.emulator ? 'emulator' : 'device'}`; - let initialStopReason = iosOrAndroid(platform, 'pause', 'step'); - let breakpointStopReason = iosOrAndroid(platform, 'breakpoint', 'step'); - let exceptionStopReason = iosOrAndroid(platform, 'exception', 'step'); - - it(`${meta} should stop on the first line after launch`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.start(); - return scenario.client.assertStoppedLocation(initialStopReason, { line: 1 }); - }); - - it(`${meta} should disconnect`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - return scenario.start().then(() => { - return scenario.client.disconnectRequest(); - }); - }); - - it(`${meta} should stop on debugger statement`, () => { - let appRoot = context.getAppPath('DebuggerStatement'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - // continue after first stop - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, 'step', { line: 4 }); - }); - - it(`${meta} should stop on breakpoint`, () => { - let appRoot = context.getAppPath('JsApp'); - let bpPath = path.join(appRoot, 'app', 'main-page.js'); - let bpLine = 5; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 4 }); - }); - - it.skip(`${meta} should stop on breakpoint in file with spaces in its name`, () => { - let appRoot = context.getAppPath('TestApp1'); - let bpPath = path.join(appRoot, 'app', 'file with space in name.js'); - let bpLine = 5; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 0 }); - }); - - it(`${meta} should stop on conditional breakpoint when the condition is true`, () => { - let appRoot = context.getAppPath('TestApp1'); - let bpPath = path.join(appRoot, 'app', 'conditionalBreakpoint.js'); - let bpLine = 2; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine, condition: 'a === 3' }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 0 }) - .then(response => { - const frame = response.body.stackFrames[0]; - return scenario.client.evaluateRequest({ context: 'watch', frameId: frame.id, expression: 'a' }).then(response => { - assert.equal(response.body.result, 3, 'a !== 3'); - return response; - }); - }); - }); - - it(`${meta} should stop on typescript breakpoint`, () => { - let appRoot = context.getAppPath('TsApp'); - let bpPath = path.join(appRoot, 'app', 'main-page.ts'); - let bpLine = 9; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(bpPath, [{ line: bpLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: bpPath, line: bpLine, column: 4 }); - }); - - it(`${meta} should stop on typescript even if breakpoint was set in JavaScript`, () => { - let appRoot = context.getAppPath('TsApp'); - let jsPath = path.join(appRoot, 'app', 'main-page.js'); - let jsLine = 7; - let tsPath = path.join(appRoot, 'app', 'main-page.ts'); - let tsLine = 9; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(jsPath, [{ line: jsLine }]); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: tsPath, line: tsLine, column: 4 }); - }); - - it(`${meta} should stop on caught error`, () => { - let appRoot = context.getAppPath('TestApp2'); - let breakpointColumn = iosOrAndroid(platform, 35, 4); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.setExceptionBreakpointsRequest({ filters: ['all'] }); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, exceptionStopReason, { path: path.join(appRoot, 'app', 'app.js'), line: 3, column: breakpointColumn }); - }); - - it.skip(`${meta} should stop on uncaught error`, () => { - let appRoot = context.getAppPath('TestApp2'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.setExceptionBreakpointsRequest({ filters: ['uncaught'] }); - }); - - scenario.start(); - return scenario.client.assertNthStoppedLocation(2, exceptionStopReason, { path: path.join(appRoot, 'app', 'app.js'), line: 9, column: 24 }); - }); - - it(`${meta} should receive output event when console.log is called`, () => { - let appRoot = context.getAppPath('TestApp1'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.afterLaunchRequest = scenario.afterLaunchRequest.then(() => { - return scenario.client.onNextTime('output').then(e => { - let event = e as DebugProtocol.OutputEvent; - assert.equal(event.body.category, 'stdout', 'message category mismatch'); - assert.equal(event.body.output.startsWith('console.log called'), true, 'message mismatch'); - }); - }); - scenario.start(); - return scenario.afterLaunchRequest; - }); - - it(`${meta} should receive 2 output events when console.log is called twice with the same message`, () => { - let appRoot = context.getAppPath('TestApp1'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.afterLaunchRequest = scenario.afterLaunchRequest.then(() => { - let assertOutputEvent = e => { - let event = e as DebugProtocol.OutputEvent; - assert.equal(event.body.category, 'stdout', 'message category mismatch'); - assert.equal(event.body.output.startsWith('console.log called'), true, 'message mismatch'); - }; - - return Promise.all([ - scenario.client.onNthTime(1, 'output').then(assertOutputEvent), - scenario.client.onNthTime(2, 'output').then(assertOutputEvent)]); - }); - - scenario.start(); - return scenario.afterLaunchRequest; - }); - - it(`${meta} should step over`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 12; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return Promise.all([ - scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: filePath, line: bpLine, column: 4 }).then(() => { - return scenario.client.nextRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(3, 'step', { path: filePath, line: bpLine + 1, column: 4 }).then(() => { - return scenario.client.nextRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(4, 'step', { path: filePath, line: bpLine + 2, column: 4 }) - ]); - }); - - it(`${meta} should step in`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 14; - - let firstStepExpected = iosOrAndroid(platform, { path: filePath, line: 3, column: 19 }, { path: filePath, line: 4, column: 4 }); - let secondStepExpected = iosOrAndroid(platform, { path: filePath, line: 4, column: 4 }, { path: filePath, line: 7, column: 8 }); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return Promise.all([ - scenario.client.assertNthStoppedLocation(2, breakpointStopReason, { path: filePath, line: bpLine, column: 4 }).then(() => { - return scenario.client.stepInRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(3, 'step', firstStepExpected).then(() => { - return scenario.client.stepInRequest({ threadId: 1 }); - }), - scenario.client.assertNthStoppedLocation(4, 'step', secondStepExpected) - ]); - }); - - it(`${meta} should pause`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 14; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - - return scenario.start().then(() => { - return scenario.client.pauseRequest({ threadId: 1 }); - }); - }); - - it(`${meta} should evaluate expression when stopped on breakpoint`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 12; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return scenario.client.onNthTime(2, 'stopped').then(() => { - return scenario.client.stackTraceRequest({ threadId: 1, levels: 20 }).then((response) => { - return scenario.client.evaluateRequest({ - expression: 'getMessage(-5)', - context: 'watch', - frameId: response.body.stackFrames[0].id - }) - .then((response) => { - assert.equal(response.body.result, '"Hoorraaay! You unlocked the NativeScript clicker achievement!"', 'result mismatch'); - }); - }); - }); - }); - - it(`${meta} should return all threads`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - - return scenario.start().then(() => { - scenario.client.threadsRequest().then(response => { - assert.deepEqual(response.body.threads, [{ id: 1, name: 'Thread 1' }]); - }); - }); - }); - - it(`${meta} should return stack trace`, () => { - let appRoot = context.getAppPath('JsApp'); - let filePath = path.join(appRoot, 'app', 'main-view-model.js'); - let bpLine = 13; - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - scenario.beforeConfigurationDoneRequest = scenario.beforeConfigurationDoneRequest.then(() => { - return scenario.client.assertSetBreakpoints(filePath, [{ line: bpLine }]); - }); - - scenario.start(); - - return scenario.client.onNthTime(2, 'stopped').then(e => { - return scenario.client.stackTraceRequest({ threadId: e.body.threadId }).then(response => { - let stackFrames = response.body.stackFrames; - let firstFrame = stackFrames[0]; - let lastFrame = stackFrames[stackFrames.length - 1]; - let expectedStackFramesCount = iosOrAndroid(platform, 22, 10); - assert.equal(stackFrames.length, expectedStackFramesCount, 'wrong stack frames count'); - assert.equal(firstFrame.name, 'createViewModel', 'wrong top frame name'); - assert.equal(firstFrame.source.path, filePath, 'wrong top frame path'); - assert.equal(lastFrame.name, 'promiseReactionJob', 'wrong last frame name'); - }); - }); - }); - - it(`${meta} should attach`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - scenario.client.onNextTime('stopped').then(e => scenario.client.continueRequest({ threadId: 1 })); - - return scenario.start().then(() => { - return scenario.client.disconnectRequest().then(() => { - return scenario.client.attachRequest(Scenario.getDefaultAttachArgs(platform, appRoot, config.emulator)); - }); - }); - }); - - // iOS specifc tests - if (platform == 'ios') { - it(`${meta} should not hang on evaluating watch expression on call frame with unknown source`, () => { - let appRoot = context.getAppPath('JsApp'); - - let scenario = new Scenario(dc); - scenario.launchRequestArgs = Scenario.getDefaultLaunchArgs(platform, appRoot, config.emulator); - - - return Promise.all([ - scenario.start(), - scenario.client.onNextTime('stopped').then(e => { - return scenario.client.stackTraceRequest({ threadId: e.body.threadId }).then(response => { - let callFrame = response.body.stackFrames.filter(callFrame => !callFrame.source.path && !callFrame.source.sourceReference)[0]; - return scenario.client.evaluateRequest({ expression: 'Math.random()', frameId: callFrame.id, context: 'watch' }).then(response => { - assert.fail(undefined, undefined, 'Evaluate request should fail', undefined); - }, response => { - assert.equal(response.message, '-', 'error message mismatch'); - }); - }); - }) - ]); - }); - } - }); -}); \ No newline at end of file diff --git a/src/tests/config/mac.json b/src/tests/config/mac.json deleted file mode 100644 index 45dc5e1..0000000 --- a/src/tests/config/mac.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "platforms": ["android", "ios"], - "emulator": false, - "port": 4712, - "skipSuitePrepare": false -} \ No newline at end of file diff --git a/src/tests/config/mocha.opts b/src/tests/config/mocha.opts index ed83f01..0ae6543 100644 --- a/src/tests/config/mocha.opts +++ b/src/tests/config/mocha.opts @@ -1,4 +1,7 @@ --colors --ui tdd --timeout 60000 ---reporter spec \ No newline at end of file +--reporter spec +--exit + +./out/tests/**/*.tests.js \ No newline at end of file diff --git a/src/tests/config/win.json b/src/tests/config/win.json deleted file mode 100644 index b6e0393..0000000 --- a/src/tests/config/win.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "platforms": ["android"], - "emulator": false, - "port": 4712, - "skipSuitePrepare": false -} \ No newline at end of file diff --git a/src/tests/nativeScriptDebugAdapter.tests.ts b/src/tests/nativeScriptDebugAdapter.tests.ts new file mode 100644 index 0000000..858e142 --- /dev/null +++ b/src/tests/nativeScriptDebugAdapter.tests.ts @@ -0,0 +1,204 @@ +import { EventEmitter } from 'events'; +import * as _ from 'lodash'; +import * as sinon from 'sinon'; +import { ChromeDebugAdapter } from 'vscode-chrome-debug-core'; +import { Event } from 'vscode-debugadapter'; +import * as extProtocol from '../common/extensionProtocol'; +import { nativeScriptDebugAdapterGenerator } from '../debug-adapter/nativeScriptDebugAdapter'; + +const customMessagesResponses = { + workspaceConfigService: { + tnsPath: 'tnsPathMock', + }, +}; + +const defaultArgsMock: any = { + appRoot: 'appRootMock', + diagnosticLogging: true, + platform: 'android', + request: 'attach', + stopOnEntry: true, + tnsArgs: [ 'mockArgs'], + watch: true, +}; + +const mockConstructor = (mockObject: any): any => { + return function() { + return mockObject; + }; +}; + +describe('NativeScriptDebugAdapter', () => { + let nativeScriptDebugAdapter: any; + let chromeSessionMock: any; + let chromeConnectionMock: any; + let projectMock: any; + let nativeScriptCliMock: any; + let cliCommandMock: any; + let pathTransformerMock: any; + + beforeEach(() => { + chromeSessionMock = { + sendEvent: (e: Event) => { + const request = (e as any).body as extProtocol.IRequest; + + if (e.event === extProtocol.NS_DEBUG_ADAPTER_MESSAGE) { + const result = customMessagesResponses[request.service] && customMessagesResponses[request.service][request.method]; + + (nativeScriptDebugAdapter as any).onExtensionResponse({ requestId: request.id, result }); + } + }, + }; + + chromeConnectionMock = { + attach: () => Promise.resolve({}), + }; + + cliCommandMock = { + tnsOutputEventEmitter: { + on: (event, callback) => { + callback(); + }, + }, + }; + + pathTransformerMock = { + attach: () => ({}), + clearTargetContext: () => ({}), + setTargetPlatform: () => ({}), + }; + + projectMock = { + attach: () => cliCommandMock, + debug: () => cliCommandMock, + }; + + nativeScriptCliMock = { + executeGetVersion: () => 'cliVersionMock', + }; + + const projectClass: any = function(appRoot, cli) { + return _.merge({ + appRoot, + cli, + }, projectMock); + }; + + const nativeScriptDebugAdapterClass = nativeScriptDebugAdapterGenerator(projectClass, projectClass, mockConstructor(nativeScriptCliMock)); + + nativeScriptDebugAdapter = new nativeScriptDebugAdapterClass( + { chromeConnection: mockConstructor(chromeConnectionMock), pathTransformer: mockConstructor(pathTransformerMock) }, + chromeSessionMock, + ); + + ChromeDebugAdapter.prototype.attach = () => Promise.resolve(); + }); + + const platforms = [ 'android', 'ios' ]; + const launchMethods = [ 'launch', 'attach' ]; + + platforms.forEach((platform) => { + launchMethods.forEach((method) => { + const argsMock = _.merge({}, defaultArgsMock, { platform, request: method }); + + it(`${method} for ${platform} should raise debug start event`, async () => { + const spy = sinon.spy(chromeSessionMock, 'sendEvent'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ event: extProtocol.BEFORE_DEBUG_START })); + }); + + it(`${method} for ${platform} should call analyticsService`, async () => { + const spy = sinon.spy(chromeSessionMock, 'sendEvent'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ + body: { + args: [method, platform], + method: 'launchDebugger', + service: 'analyticsService', + }, + event: extProtocol.NS_DEBUG_ADAPTER_MESSAGE, + })); + }); + + it(`${method} for ${platform} should call project setTargetPlatform`, async () => { + const spy = sinon.spy(pathTransformerMock, 'setTargetPlatform'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, argsMock.platform); + }); + + it(`${method} for ${platform} should set debug port`, async () => { + const port = 1234; + + sinon.stub(cliCommandMock.tnsOutputEventEmitter, 'on').callsFake((event, callback) => callback(port)); + const spy = sinon.spy(ChromeDebugAdapter.prototype, 'attach'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ + port, + })); + }); + + it(`${method} for ${platform} should translate args to chrome debug args`, async () => { + const spy = sinon.spy(ChromeDebugAdapter.prototype, 'attach'); + + await nativeScriptDebugAdapter[method](argsMock); + + sinon.assert.calledWith(spy, sinon.match({ + trace: true, + webRoot: 'appRootMock', + })); + }); + + it(`${method} for ${platform} - after process exit should send Terminate event`, async () => { + const spy = sinon.spy(chromeSessionMock, 'sendEvent'); + const fakeEmitter = { + on: () => ({}), + }; + + cliCommandMock.tnsProcess = new EventEmitter(); + cliCommandMock.tnsProcess.stderr = fakeEmitter; + cliCommandMock.tnsProcess.stdout = fakeEmitter; + + await nativeScriptDebugAdapter.attach(argsMock); + cliCommandMock.tnsProcess.emit('close', -1); + + sinon.assert.calledWith(spy, sinon.match({ + event: 'terminated', + })); + }); + }); + }); + + it('attach should call project attach method with correct args', async () => { + const attachSpy = sinon.spy(projectMock, 'attach'); + const debugSpy = sinon.spy(projectMock, 'debug'); + + const argsMock = _.merge({}, defaultArgsMock, { request: 'attach' }); + + await nativeScriptDebugAdapter.attach(argsMock); + + sinon.assert.calledOnce(attachSpy); + sinon.assert.calledWith(attachSpy, argsMock.tnsArgs); + sinon.assert.notCalled(debugSpy); + }); + + it('launch should call project debug method with correct args', async () => { + const attachSpy = sinon.spy(projectMock, 'attach'); + const debugSpy = sinon.spy(projectMock, 'debug'); + + const argsMock = _.merge({}, defaultArgsMock, { request: 'launch' }); + + await nativeScriptDebugAdapter.launch(argsMock); + + sinon.assert.calledOnce(debugSpy); + sinon.assert.calledWith(debugSpy, { stopOnEntry: argsMock.stopOnEntry, watch: argsMock.watch }, argsMock.tnsArgs); + sinon.assert.notCalled(attachSpy); + }); +}); diff --git a/src/tests/nativeScriptPathTransformer.tests.ts b/src/tests/nativeScriptPathTransformer.tests.ts new file mode 100644 index 0000000..8ec9f43 --- /dev/null +++ b/src/tests/nativeScriptPathTransformer.tests.ts @@ -0,0 +1,34 @@ +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as sinon from 'sinon'; +import { NativeScriptPathTransformer } from '../debug-adapter/nativeScriptPathTransformer'; +import * as tests from './pathTransformData'; + +describe('NativeScriptPathTransformer', () => { + let nativeScriptPathTransformer: any; + let existsSyncStub; + + before(() => { + nativeScriptPathTransformer = new NativeScriptPathTransformer(); + }); + + describe('targetUrlToClientPath() method', () => { + const webRoot = 'C:\\projectpath'; + + for (const test of tests as any) { + it(`should transform [${test.platform}] device path ${test.scriptUrl} -> ${test.expectedResult}`, async () => { + nativeScriptPathTransformer.setTargetPlatform(test.platform); + existsSyncStub = sinon.stub(fs, 'existsSync').callsFake((arg: string) => arg === test.existingPath); + + const result = await nativeScriptPathTransformer.targetUrlToClientPath(webRoot, test.scriptUrl); + + assert.equal(result, test.expectedResult); + }); + } + + afterEach(() => { + existsSyncStub.restore(); + }); + }); + +}); diff --git a/src/tests/nativeScriptTargetDiscovery.tests.ts b/src/tests/nativeScriptTargetDiscovery.tests.ts new file mode 100644 index 0000000..18d6749 --- /dev/null +++ b/src/tests/nativeScriptTargetDiscovery.tests.ts @@ -0,0 +1,45 @@ +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { NativeScriptTargetDiscovery } from '../debug-adapter/nativeScriptTargetDiscovery'; + +describe('NativeScriptTargetDiscovery', () => { + let nativeScriptTargetDiscovery: NativeScriptTargetDiscovery; + let stub; + + before(() => { + nativeScriptTargetDiscovery = new NativeScriptTargetDiscovery(); + }); + + it(`getTarget returns correct target`, async () => { + const address = 'localhost'; + const port = 41000; + + const target = await nativeScriptTargetDiscovery.getTarget(address, port); + + assert.equal(target.webSocketDebuggerUrl, `ws://${address}:${port}`); + assert.equal(target.devtoolsFrontendUrl, `chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=${address}:${port}`); + }); + + it(`getTargets calls getTarget`, async () => { + const testTarget = { + devtoolsFrontendUrl: 'url', + webSocketDebuggerUrl: 'socket', + }; + const address = 'localhost'; + const port = 41000; + + stub = sinon.stub(nativeScriptTargetDiscovery, 'getTarget').callsFake(() => Promise.resolve(testTarget)); + const targets = await nativeScriptTargetDiscovery.getAllTargets(address, port); + + sinon.assert.calledOnce(stub); + sinon.assert.calledWith(stub, address, port); + assert.equal(targets.length, 1); + assert.deepEqual(targets[0], testTarget); + }); + + afterEach(() => { + if (stub) { + stub.restore(); + } + }); +}); diff --git a/src/tests/nsDebugClient.ts b/src/tests/nsDebugClient.ts deleted file mode 100644 index 57581f4..0000000 --- a/src/tests/nsDebugClient.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as assert from 'assert'; -import {DebugProtocol} from 'vscode-debugprotocol'; -import {DebugClient} from 'vscode-debugadapter-testsupport'; - -export class NsDebugClient extends DebugClient { - private _timeout: number = 90000; - - public getTimeout(): number { - return this._timeout; - } - - public setTimeout(timeout: number) { - this._timeout = timeout; - } - - // The method adds the following enhancements to its base implementation - // 1. It has no hardcoded value for the timeout - // 2. It removes the event listener after it is no needed anymore - public waitForEvent(eventType: string, timeout?: number): Promise { - timeout = timeout || this._timeout; - return new Promise((resolve, reject) => { - let eventListener: (event: DebugProtocol.Event) => any = (event) => { - resolve(event); - this.removeListener(eventType, eventListener); - }; - this.on(eventType, eventListener); - if (timeout) { - setTimeout(() => { - reject(new Error("no event '" + eventType + "' received after " + timeout + " ms")); - }, timeout); - } - }); - } - - public onNextTime(event: string): Promise { - return this.waitForEvent(event); - } - - public onNthTime(n: number, event: string): Promise { - return n == 1 ? - this.onNextTime(event) : - this.onNextTime(event).then(e => this.onNthTime(--n, event)); - } - - public assertSetBreakpoints(path: string, breakpoints: { line: number, condition?: string }[]): Promise { - return this.setBreakpointsRequest({ - lines: breakpoints.map(b => b.line), - breakpoints: breakpoints, - source: { path: path } - }) - .then(response => { - response.body.breakpoints.forEach((bp, i, a) => { - assert.equal(bp.verified, true, 'breakpoint verification mismatch: verified'); - assert.equal(bp.line, breakpoints[i].line, 'breakpoint verification mismatch: line'); - //assert.equal(bp.column, breakpointColumn, 'breakpoint verification mismatch: column'); - }); - return Promise.resolve(); - }); - } - - public assertNthStoppedLocation(n: number, reason: string, expected: { path?: string; line?: number; column?: number; }) - : Promise { - return n == 1 ? - this.assertStoppedLocation(reason, expected) : - this.onNextTime('stopped').then(e => this.assertNthStoppedLocation(--n, reason, expected)); - } -} \ No newline at end of file diff --git a/src/tests/pathTransformData.ts b/src/tests/pathTransformData.ts new file mode 100644 index 0000000..f767a6f --- /dev/null +++ b/src/tests/pathTransformData.ts @@ -0,0 +1,19 @@ +const tests = [ + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/main.js', expectedResult: 'C:\\projectpath\\app\\main.js', existingPath: 'C:\\projectpath\\app\\main.js' }, + { platform: 'android', scriptUrl: 'VM1', expectedResult: 'VM1' }, + { platform: 'android', scriptUrl: 'native prologue.js', expectedResult: 'native prologue.js' }, + { platform: 'android', scriptUrl: 'v8/gc', expectedResult: 'v8/gc' }, + { platform: 'android', scriptUrl: 'VM25', expectedResult: 'VM25' }, + { platform: 'android', scriptUrl: '/data/data/org.nativescript.TabNavigation/files/internal/ts_helpers.js', expectedResult: '/data/data/org.nativescript.TabNavigation/files/internal/ts_helpers.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/nativescript-angular/platform.js', expectedResult: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform.js', existingPath: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/nativescript-angular/platform-common.js', expectedResult: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform-common.js', existingPath: 'C:\\projectpath\\node_modules\\nativescript-angular\\platform-common.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/@angular/common/bundles/common.umd.js', expectedResult: 'C:\\projectpath\\node_modules\\@angular\\common\\bundles\\common.umd.js', existingPath: 'C:\\projectpath\\node_modules\\@angular\\common\\bundles\\common.umd.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/gestures/gestures.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\gestures\\gestures.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\gestures\\gestures.android.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/frame/fragment.transitions.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\frame\\fragment.transitions.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\frame\\fragment.transitions.android.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/debugger/devtools-elements.common.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\debugger\\devtools-elements.common.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\debugger\\devtools-elements.common.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/page/page.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\page\\page.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\page\\page.android.js' }, + { platform: 'android', scriptUrl: 'file:///data/data/org.nativescript.TabNavigation/files/app/tns_modules/tns-core-modules/ui/layouts/layout-base.js', expectedResult: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\layouts\\layout-base.android.js', existingPath: 'C:\\projectpath\\node_modules\\tns-core-modules\\ui\\layouts\\layout-base.android.js' }, + { platform: 'android', scriptUrl: 'ng:///css/0/data/data/org.nativescript.TabNavigation/files/app/tabs/tabs.component.scss.ngstyle.js', expectedResult: 'ng:///css/0/data/data/org.nativescript.TabNavigation/files/app/tabs/tabs.component.scss.ngstyle.js' }, +]; + +export = tests; diff --git a/src/tests/scenario.ts b/src/tests/scenario.ts deleted file mode 100644 index badbf5d..0000000 --- a/src/tests/scenario.ts +++ /dev/null @@ -1,126 +0,0 @@ - -import {DebugProtocol} from 'vscode-debugprotocol'; -import {NsDebugClient} from './nsDebugClient'; - -export class Scenario { - private resolveStarted: () => any; - - private resolveBeforeLaunch: () => any; - private resolveBeforeAttach: () => any; - private resolveBeforeConfigurationDone: () => any; - - private resolveAfterLaunch: () => any; - private resolveAfterAttach: () => any; - private resolveAfterConfigurationDone: () => any; - - public client: NsDebugClient; - - public started: Promise = new Promise(r => this.resolveStarted = r); - public initializeRequest: Promise; - public launchRequest: Promise; - public attachRequest: Promise; - public configurationDoneRequest: Promise; - - // The corresponding command will not start before these promises are resolved - public beforeLaunchRequest: Promise = new Promise(r => this.resolveBeforeLaunch = r); - public beforeAttachRequest: Promise = new Promise(r => this.resolveBeforeAttach = r); - public beforeConfigurationDoneRequest: Promise = new Promise(r => this.resolveBeforeConfigurationDone = r); - - // The corresponding command promise will not be resolved/rejected before these promises are resolved - public afterLaunchRequest: Promise = new Promise(r => this.resolveAfterLaunch = r); - public afterAttachRequest: Promise = new Promise(r => this.resolveAfterAttach = r); - public afterConfigurationDoneRequest: Promise = new Promise(r => this.resolveAfterConfigurationDone = r); - - public initializedEvent: Promise; - public firstStoppedEvent: Promise; - - public initializeRequestArgs: DebugProtocol.InitializeRequestArguments; - public launchRequestArgs: DebugProtocol.LaunchRequestArguments; - public attachRequestArgs: DebugProtocol.AttachRequestArguments; - public configurationDoneArgs: DebugProtocol.ConfigurationDoneArguments; - - public attachInsteadOfLaunch: boolean; - - public static getDefaultInitArgs(): DebugProtocol.InitializeRequestArguments { - return { - adapterID: 'nativescript', - linesStartAt1: true, - columnsStartAt1: true, - pathFormat: 'path' - }; - } - - public static getDefaultLaunchArgs(platform: string, appRoot: string, emulator: boolean): DebugProtocol.LaunchRequestArguments { - let args = { - platform: platform, - request: "launch", - appRoot: appRoot, - sourceMaps: true, - emulator: emulator, - tnsArgs: process.env.DeviceId ? ['--device', process.env.DeviceId] : [] - }; - return args; - } - - public static getDefaultAttachArgs(platform: string, appRoot: string, emulator: boolean): DebugProtocol.LaunchRequestArguments { - let args = { - platform: platform, - request: "attach", - appRoot: appRoot, - sourceMaps: true, - emulator: emulator, - tnsArgs: process.env.DeviceId ? ['--device', process.env.DeviceId] : [] - }; - return args; - } - - constructor(client: NsDebugClient) { - this.client = client; - - this.initializedEvent = this.client.onNextTime('initialized'); - this.firstStoppedEvent = >this.client.onNextTime('stopped'); - - this.initializeRequest = this.started.then(() => { - return this.client.initializeRequest(this.initializeRequestArgs || Scenario.getDefaultInitArgs()); - }); - - this.launchRequest = this.initializeRequest.then(() => { - if (!this.attachInsteadOfLaunch) { - this.resolveBeforeLaunch(); - return this.beforeLaunchRequest.then(() => { - return this.client.launchRequest(this.launchRequestArgs).then(launchResponse => { - this.resolveAfterLaunch(); - return this.afterLaunchRequest.then(_ => launchResponse); - }); - }); - } - }); - - this.attachRequest = this.initializeRequest.then(() => { - if (this.attachInsteadOfLaunch) { - this.resolveBeforeAttach(); - return this.beforeAttachRequest.then(() => { - return this.client.attachRequest(this.attachRequestArgs).then(attachResponse => { - this.resolveAfterAttach(); - return this.afterAttachRequest.then(_ => attachResponse); - }); - }); - } - }); - - this.configurationDoneRequest = Promise.all<{}>([this.launchRequest, this.attachRequest, this.initializedEvent]).then(() => { - this.resolveBeforeConfigurationDone(); - return this.beforeConfigurationDoneRequest.then(() => { - return this.client.configurationDoneRequest(this.configurationDoneArgs).then(confDoneResponse => { - this.resolveAfterConfigurationDone(); - return this.afterConfigurationDoneRequest.then(_ => confDoneResponse); - }); - }); - }); - } - - public start(): Promise<{}> { - this.resolveStarted(); - return Promise.all<{}>([this.configurationDoneRequest, this.firstStoppedEvent]); - } -} \ No newline at end of file diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/AndroidManifest.xml b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/AndroidManifest.xml deleted file mode 100644 index 8d827dc..0000000 --- a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/AndroidManifest.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/app.gradle b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/app.gradle deleted file mode 100644 index 725fb59..0000000 --- a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/app.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Add your native dependencies here: - -// Uncomment to add recyclerview-v7 dependency -//dependencies { -// compile 'com.android.support:recyclerview-v7:+' -//} - -android { - defaultConfig { - generatedDensities = [] - } - aaptOptions { - additionalParameters "--no-version-vectors" - } -} diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-hdpi/icon.png b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-hdpi/icon.png deleted file mode 100755 index 1034356e2641a785ae5182f97d86293c7d1e572a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10946 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 lmDvf8z`|5dEfUn+mCXE9vpxzu&r{|EZkbKn2~ diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-mdpi/icon.png b/src/tests/testdata/DebuggerStatement/app/App_Resources/Android/drawable-mdpi/icon.png deleted file mode 100755 index 486e41091b02089223650cb0a490a3d598a7a666..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7594 zcmb_>2{_d47q+xel3v-8GDeYgRx@TSWj4u@?0Zc!gJH~!F*1aCRftlFRJ5ol$x@Pi z36Xu@6(RN7izHEfqecDStMXpo_qnbyzvVvnIp;agb3f;~Cd|}CUwEDLIzB!=VS_z5 zGu}6N@gul~_ura-E|mARnT@w$n=_o*K13FUPmjzvL;)Ch5M3x{6e8Kzvx=h4GbU3l zY}hu&MrablL!G#YQRjFtdDeV<+PgSRBFUY?1{|WeQ0Y3r$^2p9)VnOnAC!xr|UAh2m4w^^cL17aC91j}Z3(e61e&I#)t`{GJfPgO$w!04S zYfv`ErT{F1MFD82Yp9XHPzV5tQio{3Q4okK01Ae{KoBqpic*6>&~PLgf&hH`1MZ?+DQ7wOj&JeW(gbgys2#2YXWhsXp$)WM4>eF2h5OE{)C zi}uAinFONHC>|6#+lz;VEMb|h3^v2dmGK+urPu%CfH$;9GguZ32JL&5 zO~2<0z+wSAjfo^GeKCKfMQ`5>C^#aUq66fW9HItBszG2D5GWc9M#GRQ;KhrtP-6y} z>g>A&)ldV&)xeq-5Cj?qMng4Jz_1@s-h3ky*~DeRWD?q$!SW#TMvJ<*L_kcs%NK9P z#%Ke&7n?{YQ4DZ8KwhZoR4N&bM`$AP8W0T}0>%p#568fuU@U=v)x;w(U?>#!-4@3n zc`ug!yDj;LEn+#_eJm<(*%4{K;y__EFjy1@0oH_q5tc?BqGAu|a z2MGnZl9|-s5d8#Ljthn0pb!AgAIN@2EXM|c!#TVUfb%E9e}R_eL_s+a6qLvLN78?x zmScr$a^RW(kKd2sPk`mPpl}Wp{wJ=V0LyW~5F8i+K>K}MKLM8Gf@pFent$T@39uZO zg)7wyz@~UGX*@9j_z=ASq(w2JkO7B$0mf94E0ID25NJe}Hxpn=O4IM zC=flES^&M@OxFJo%uFVg#GrqVZqX-jxqS5KWEO);1{e{&=_FSQ3t+%w09brakhi7$ zCfxsExSVfW-mU;3GMT^eu=IM72ei0xeiatb4@vnS>G|>ayQb;!bk&QuQT$4nbiKWK zY6-)kz&xFU!wFaf1cJk$G;k^fW;zVyhz}11pX(> zU)ShY#rV=_p!ZN0i{txkY|Sa2OQQ_<(p#X3q{W^|2l#D?0lz(_{$k5qcFX!^>*Gq{ z5&oeE`0Cl~TVt!|!rNYd(PNlv0G4`;pV!#`#_dHsOd~C^#70t1&nfr!I7G32sp1@f@^9b)HIP4q8f@wLZS|l zU=X4P<@-1+RbZ*LLZYEaGz{^*wfb+l{Hx3RYV@yUzD&rk^9uCuDfjgrM`2NUH6pNh z^8BSCMgMAY{nzw(9V(jqi=J%R$+@I(KOGgmiyrU%v3R+3@ZsGo9i1q2Ug220gVF3- z&q_W%k*5YYJqu3vW+U^+C(9|=!sUCnnb#zKJ2$R@ojU~XO4$QyW>YK9nXyTJxC8=<+YF6#g8i1&F|nY;5IsCpHIIAv7tXL)UTf4 zcGf8Bs%CtSuPj;#EK4>~b0|5<5ifgEytU@i{x$EDn=~9dFphn#Gcd>O(eB=9OqRo4 z_mgQN+^8>zj*v=gM=NA3oB>)pZ8vjLZw_o;wKv^gj$?>Mk5B4}14mGI zQnSim>773;(!uo5<=SnSznE5+M<1zMIZ&wDQm21KTd8k+EZY8_7uhB5<5**$ z{D>Gr^5nwQFmvC=kiIr^XtGm6O6@9awwe18E%}s;TJ7VCx!E_%dasD-v_CPd1Ae}4 zxI*2>N9lEBgG6bF^1|5kt4cMl=hdT(lWXmhAD^&`d?g;);V8!{na2fA7wmD2h^l6R z@_QlIyTv=k$AKfc_xz&;Yi>IDUh=(xDb!@2*n=$`-+y(QpRHo~xOV1lC6aV%A$nK! zX_1fNnOh@cx7KB3%_Nbn9RPaqW%{(Tx1}r_pXnZ8hr@`4pdv~<$+-U^{bb1iSwHO2 zrL|kFocoA^t30%lH)ErAdkFV>^RvV5%0DrBkK^t6zylG13AFBlbmn`J5X+j& zSLzNw%5}LT314S|n^jIpm^snUtpr47A66z4E*{jf?DBax%s`o+b=Ue}vk-IUYQKyQ zZ0E;jx0*w{|JniG272Ds;Us;b#X-UF8H~HLHh0kJbN6vV+4xn7$T`U_xqXG(+GFBjmBb_eK+G0t;aNWK&V5jmzkUbpG(mJ`zErM=cMlSgB8qzQ<0*A#7LqRC zs`S;7FD6XCyEg0AW{T9AD>Jf6os~1yZ!d=r*V?Zd>^63q_-J-ZJQolLf24T&;qI}? zV9OxNi3oq~UAMX->@~K9aOS#i!y@iuPHajWzjM4|F!R(xUyDx8IexhJQS*f(5A%e$ zUN$k%a7A%+SC%2$H+Qn;lEodx*{q8jS<yB4T~LbdntU8KQ4YJ2 zh(6htlXDGeEozi={;1c&@eB8b@tlK&s&p%BlEaCGzy?VTME80p9g$JX!#XD|WRi11 z6JZz3_iJ-Ho7K3t^DL6F5>@7spQ>K%UhO`q^-tS*E6F|ANz$i|-`}iI^W=G{RzuO) zP(haoeLi98?D$GpkxG}mpsf39r+T1~{RZD~TGCq`rS*t^oCDz7v@0cIZXXI^rSdaA z!^$fR0#a9_ZlgacK5vc_h`#Ukn!+lx1ake{cdxF}-nRCDsBVBr!gI?bk%^_rJ`L|j zZau0pIkw74vz;!%#}zE~(TWQ?CnO&0UQ>PO?WjGzKD;r!qtO3jyDAwxAg}j=4G_zT ze9&ilf*ho$(iShUVM}z&LtCYOj9_~|;)wpe`lDNMR{*b&lhe>)BT3cvrv86wRCN-8>8%~zz>Gcb9>F>uuS>H{O^=^gbKT!Zkm#E{gL#BgtoZ9ugnc-tlAayxks|_GOVRa zQbNnNxcr?ao>lJWy=%p#ZCMu%n9}x! zbl9wW;SAiScDI4adg;obheRH(I9-$fJlHO=j6r)R_sFc{{F!oU&4$Q*0Z$D=`TZZh z3)oa4Vg3dpUgRl;Z=u>#tN7A=D(tWrx$~O}B(IUcr61yt2Pi6^ij#gwP>RSR+^kjg zN>cL!7Rd`4j8FhIh!&W`d}_ek29M3DruWbi$6Qmy~z3 z4(VgroBBM>-ul%LZ^BKb)=Pglhj#Zs7JDe^h9X?GELNtuC9S#~Npb2qIJPNvp+2yT zTk29RDzh-mRjYhOuf|;M&3jO}H+RiV(@lH$4CzePN0)$3A-sr8#>e=zQc1AB0Pb0^ z3)=XsdDh6viLf2Ur#CR?r^*GgL zCWfozD$>ti{OXhc3q(Owm{n2#3{r8ncjD+!*n?JC0H?^*0zD?{E}}i^OevA9({L*Z z;0)0s)K0B*sg8`zSzGgF2j$2_E$-SlAICQqk#p{?zI7I}2tt_pte?)Fs=4josZtZ( zflRtF+V^(Ky1VMx3@TE9+fNx--_!}!Z8M$B_j{SSYwyAFg21`aN$RVqoC_VuaHGu( zoyXJZNx+fo>vI~7UYneKZ32=1+r;&%NGh>*rZFt~=6zG&lr0Z#yWac|00P|=6;jXM z7Bt#ClOl&rF#{F#Rz(P9$~E~xa+==>&Kth@z?LYc=J&Lv%E`J0Ig@N&upgvVWhV4T7sZ4`!z4BbzX43hqr1|t0<~B z*t;*FF0`c0L5;l=R*IP1Ra2IxM{&o>J`|v|ZRfBKdBXCM7z` z*(xSjc}#!~v^Om(TcsxXCJu^KKh)PXD`y_v^N=bVe>S7@oK}D!;Z}gsUt~{N34Zg& zkx|-Z(HyJe;K{Mz^3P-BDmv*WBVJag7CeBcKT%e%-*aDT8^7Hu=bipL#B;Ml zwvNibz0o6dJ^L!U_mjyphZFfS!3{MV68nrJ)E|o_s3(s2U*AJiCRGnDOgVsrg;|+T z?m$+EBEr=a_*dMn8k7pwywpqQ@r1q!IFZhteiS)aWIY zMuewhU4Sy}dJ~%JDM-5xN+gW7yTxxt$#hsfhM@Ie-ZzBe_E%R)o33egPZkI${Cm}d zYqGoC+AaW}TrEm?a&Tb9_QR9qHco?$s|R!TDEhy!z$&MP4R_r-Yz|oOekOKir}Vv+ zHA90>GK1y@*PIOG_wQ0JzpjkJ=WplFDz4bN@2O|H2z#)*hL(IG(6C)$4Y5MdP~lkZ z>VK}DQiAJ>D^KEsQZltlr1b$%cte3qwbZt|d89_s(w7EM%)ZT( zLbA$6X2hfqz=2M3@4}Uy(Ou4|Q zX>6ui+MV;h=)$m%yU8(^;5?35gY4QH7arc|_2%joeGFD*TeC5j`DGL$y!zMfh|89d ze!o&%`^~sOzlIa96Nu_de-wvDN8G5jhCH2(al7jmQcft+udY8OL$CZe-Irf)-FJ`G z2YauM>YxYesl05H)auG;X&yUeu;xL`F{d{)OR1C%FUm`_Z`dV9OYIGqpxy3e2kp%a zRZKu+UH98n__Rhb?V;S}4$&=h7iac>yE|pPw`6~)p%>nLty<{Ta3ea_{Z^|DC8PQI z%RIz{_27v7zF zx4|80YZR<+cQ2jx`ef5 zu9(QR7ViVz5T}$52=;B3D?fFvYv*Nwnl=kb*|(AFTG0EaP{I{S6WYK5z5dsBil3)X z=}n~!6IAwI10}p_~a>*&%^n8FTdZ&q? zrAC4#g&AN$iN@_bzCwAGnsBvOy43bNyINO#h{Ty|Uf>ISkF35ck^e-t_(0$}?JJ{0 za!~L7lB@7RNAsb}xzanwv|L{gFl^*a_eN19g=}?o7uJ-i9{RgVc3qS5x$sNy*Oz0J z1Z>wJ&NgekqQ-x|dE@yCN_PCvYR7!PO;br9U-r0PkxBtLKhhtTbc9pcUwUhI8x6<%c(X{Y& zu;90(5fz~l`XB&f;A90gqx#_F={a z{-3eZ|JShslI~V!P#1Si7Z=C>t^!qC7pRMet&1y_q$CxSvYCaQ^XonHtB?NIl%=fP z?YyikW!znysQzPQ0lWW$0v6V0T$bDrUN%cEGj296UUM*;IfR#=4aCg}=H;_6H?sus z(EQJM%m2T|Jub}HT4(lWZN=;$c5aj{uVuTI5m6%<@pSTMR-(%Irf=KTh!TR%>FnxN9HGwloJ zjy-GXctFFmARBZWqYuDiSNhM>x~`XJMOO4lp;w}efdW^wU?dDeR)Fv8TO^D?3f9*b z@Odb(hq|CzPmEM5-ACduMO9nsTPj_^wP`kfTm8Z>-jm&^$WPAENO^Av0m8ba2yTyK z=?K2)<4$n4*JM%r0TX_k{|p{kv7_(PJK9xTCTc>ONvJx0)^)&@%!=ZZiHS051laV8`AW175->C$`6D(6qq_y&W>bkvu=GAez`;Wep}SD)pz}~ z)hofX^By3njRypWqild?oU8m$a|X6(ueBaWhe3L>Oxv!FkRa{i9|+gO z{TQ@tV*2n|$Mkq6uK(KYJ=`$bU)scr0l@LAkurgHNaR%SU!0DbQLnT8Yz~Fj8PFbc z=nY%EAl+2AByIU0Os#O0?tSo;?w_q~voF)TIpDsBAc?)vmO#%FfcOG^T16BkdVKDJ zj6--(^VD4!a#@o;?GvX6wS<*h&|Li-%WeHj9HmHE`Bc403?N2J95zIBJCUjfpp4kn{n#*WLo@b#ie`E*V zd%+3$TbW*Pj4}Jf5%ejjvgg>QId@u>+WOw~|mjX-wfy8Yl z`XVIW4?8FK)kw=f#hjInYDBCzt7ptXkC0SJ#kD}DXKxL#mg$)6{)g?`$sdk=Mkr+u zlqep{^OV*Gmj!_R80_OHldqs#xOazKHinuAPo{7%b4X}A`!DlLvLd-QK|KP4qHHedx#|^jkZF+Qz@7u-gm|cwJa=r{BEZf`ouWnzTqmNx?l!Uoa!K6W#Lv> z8ryrRiGzZ#KLwf8X zX8r@wyYx?-wnzNlZAHq?Y{L7vLrd=R02cF8Bn!6Ji1#$#qgXf3w07Ss$^v^bY$g(V6XGu8(n;^t^cS$9GwIUrX?A4qGA6lV-8DBBRjayDZW6 z1D4g0B7GV=&!RWWV{Az?XN#vl(nG6AZaFvD**|@{^vHS^nIMxCJe_=;g0JgO0ANg^ zfbu9qoaqtlyPqk3O^1{2oL60u1`?#Qqxe=61iInjh)`nv-bVr8F?de$-)1;@q>23E z<`f}rB>We+@#=aeL5_<&2%q!XSn5tDU}K%DfmkU$ z%=INP`{mpfwB^6BMr4b9{_+Xw=R-UyWq_WI7-bu^(%;J}%TgZNA$RiPG}`iAZB$fT zr9$1Wh5~GyweY*qKE{-p_yGXbBk7Gtte~=Dmw(7=kjUFdgpkVTG1IQ4HT3;*@@~cu zD?$fz}DK|E|!z&(Q! zz~l;V=LOc!#t8lzt&Mv>|IG{#pEhWrpPspW`xRgXDjPcq{o^oH3w){BIB9!-zUjU9 zv2J;fN@!qM5fc0wg~(pU+n#bs1g&`K2?my!zBA92OJ~t>YrM(2YYWztsIJHPH%J}R z=1*&X9CSHO<}P^M>%CDWC;~ZA^ABN`Tnf!Tfa~T(_^Pd`dBr9D_xw{z*~F@I9da*& z!^(jIYi`!1iPs59S${2cPs$Bw^BY~rn(rh?68lxUwoTai0H@{92^^gJv+a=xDJ<(Z zzi5k?&7hncn)H(2`USErpOARxm`wPd$`z#ekNu-e;#665HQO68$vcOMpAu@Zxt&T% zw_)YMgjEku{w{7EnYF^31$N^~)oHQG-aHd8CMj#Obk z@VD|Kd!A|?qpQsO8fh?iysfI!u8OZFO4-DM2_oLr?kjvxwR^~X4kkVV>c2QaFO10p znlm>3BcY<8+2+(tC0XV~m--hIOaRH3j>KmZxyV_^#KZt#0M{?elXoif57+Q$V$s*Irq|=%pHw% z{Pkubt;9j8<|iGSSs7yLmq2g2e{4xiNlchJsITSP`{st&}leeIkZu zT#gID!pJ5PiP4T(DOD29Q@0Wry9B*tee>s;qQD5IQ2#jys`c1XW>+iW5bc?6$bc6$ znbKi-P{>dZ6NGUxxRM}GLDQ3pW?>mri~>`v>RY4-TBVa8xDx<~;kU_?T{5g*)!u7d0P2jjhEZ-=A9;q!(n}olToWGDQp-<&O`4lbsKmxYI^k9k z^1P3#+4m86oYo8=`&su4G!{eART#Ri351-8bW`~njLbLdAD}7kyi?&}%@MWFJ&mvjU(yQ< zrS=zX4`&xmux~Df=OxyK@r=@vN2S9X``p~ol<9Rp5qRPm;ke9vboXgn%x)J~9u6)JF(wnqa);0k~|I z6R&alG-AAW(XUSxE^aO51i`UcT!%bT^D1^%!>$1*!6<*}AQBwFR5OJlf_^fA9=2rZjGB5NzhtJ2mU!6- zE}#ozoUJ+9N2)(T&M=Bfrifi2s(0&efynyy`NoMyGleh7&k2`fKUsm!#3qrIltnaL z`qXT=FPiT0o#HAqbqc&RldEnIqe?KOFx5A^?m$iDcA(%S-gM(WHBU>Dw|UMpLw3`y zpI8cb`X}^xOyk5)N1*YQ)$*9UYgT$wn8-ZxJM45OvVuN-55OnwUa<{4{?@ZA2zk6+ zyZz%a9%yTZ6W2W4xEx=c2B5GlpTiZ#aw*HkUX;FD6B(7*s_}5PJBaFZ&9&WT?Z}_WxO%Hp}y&H zU)cJtaS0%hoe|lYTrn(r=3)pwYxa{%H^=V}%gxlGSLxBl^0|O%JOk5l1zw zXMLCxO^$oPkgMvO?qNq;CBDF1T=lTMYGcmD4?g9ZQFZLq0>Jzy9J=@c^}*9o7hp7m}Fw% zU%l>4whvdeyKB3mptG%5QtFgbkYJF<@``G$Ef0a9TLNM86uJS{5nSA}Z&Kvg+SN?D zPAyY10FPF+3Ma0U~&>VL1zS;D+sXEsEN+}xl)UUg5G@|3VE}OSQaH=X|w&^)_7OY}}0GZVo zbsG$r$hR*ywRGMI?DcO$nzd^#4qQ{7$8=CHJ}&ahyfv|0HV*Ck5{^Uh5&dU)J!iW{ z5mn6-hrQ!mMXmrtc2b(_NTPuc*wowf&&P$wC~1-^Se#>G~E7igQqlOc?ZXn@o&HW3#=Yl?kytpHRp|wYu#Uc7V%is z8i&qoCW3cyiG3Gf4UsyaurP?dotwoXY><}#J;Dz_!#Yae+K9= zK>GQ9fS45$aL|@9F7JC^j`?t1v30=2ctl-FGim$prsuBI1kNykx1X~XFqt;D>~^^E z&i6Sip{%g5q78dKa3v_;o4b5SFzAv24$fcw7~d!xH=oLHh71AD#=gPr=ZB{NfFETl z;Lr)xjj?=mJvpoBDdWmv<^ryV$O*y2@9*x4aQMfPK3nF9f`{kNo3u%0?627sos;8F zDf}KishP_C@vqH?gcxYs5mny=ZvMt3&yE1xudZAZ#UIanISi(A3-MX|oFD$HQw_rD zGSh?Xx@MJa6R>fbUi74Y<*=FaDD5CbBf)SiEkvWZFG+wEVjJhcq1Ts{fG3`c_R4Sr zZn2_S>$J`78dO@9C8+>7EFqc%J$X5c{66#m6y}`K>9j>;7d??&Icz-B4WQW(sG|$W z+c8~x6fFr<#u&w1v93FCL3z*vH^1@4EgfW8N;iRWm_I3u??HG~Hk2qB^!$ZS8w>>F zuIHm+TCH6=7^c?SNVk<0(SUH&h(jvTATIal+M$z(UKD_?@?F9^W^D<Lp8`x-`~COvRTA(tK^`m_`69QI6WI>IJc)@->?_MoR->cN zGYJN}qmt?BMGzS<>6)%R5eW?pUhuw=tW{5oayIFDptE)Z&ROfG6ml_O`4@J0ef7CW zoc3PIgdxaOhm+uzWHu6@mvZ9#Y>b#CeNxIUk{aLwaHu>oo82!{!AraUQ@CKNNP$4c zojdD`=18Lhw5j#eiw5JjW94nS>XYW1dy}e%`_D?C07hC(*cV?UoP75WXQ{%}n=<0s*i}WHaEps40qB9D>vdwa+Y`ipZ2asfK7)Mby#eYrY zxiEymJ{Kl0mCmle3@UvzV&C5sAnd0|;^*6Q!JT9zmKA0d#mI7v@`(#97i6umTMI3G z`_iwZHb{0L%Jv=yR)Pn>P~^?{vnVboaR+De=ISrzKtaZybz|cW91IJa-_hx z{F<#KUKi4jA4XPHv}O3^atrI*A6v4SS>6T;!m=9(`nwKiGN60`Dz=n=d-uar;g^~W zo%u}mdW2DGxmQwRQ#`RRN$hutalltseHK#91oVr18rhI=b0Cgk3dO4$K>`z$E7A+G zi?-lS)7-x~1Hr_6QvsdlbjM7Lc00OJiK%&C`C76sCs)-H`i3A_8uoVfIK4suUyrx6 zqJ_8EKBblb?MyP(iImIdWu$7@ys%=XTbb{knC>P*I=%J2;N&Iiju!;!y-$ z>mNwNihXu&Hs=c>labxrf;0nO$zfQ!^lDv$*caB5H5K(efTuXwsayJycx7bQJ%Hb4 zWbC64F|{QaW*G?e^&8hgT#P`CjaUJx>ZX9n76gl9FPU2>tU)=GmYOp<&q%kueeib+ ztUX#mo^%ssGan3eCjB&w$+hK6205LE&kS*71MnC7YWX&2)TA z)ExI<)es#;*l*w@q*dcMeBN*>XHr0qA1s~>f!n5P zZb%eTQe0`qHZWT0C`w!$BawMmO@-ucJyLe^@k`n_$0C}-nN0mHqgfh3It6HnOfwxxiChR3wsDHB`}~D5BkAn2W{SMn3!@Vc zHd^~KFneRc?N?1+VgF2}_|26V*FasmDU&98PrQ~!riTMuYCy!H$YZlZZs@EF4sk6L=imh#UGf9 zX3~c{HT|hG1cVC1QMa2dNw^k)>D}Bk95+eiQ%SSZi8$lBDfx|J3sF^yeDd)P4mMAE z4Mw|i@6LZ(t=8*gM1;E$!xC8Uj{_!&_a#h?tF5HQqh!dr>-@v_Kw%8UG+8ZWK6zO# za2cmxJkgCFlpHFs3@XgcOP;<++PWe5W9}p2``Y;;P0xSSpVTu@O+m8u5u82791vnV zMm4bE4{cxUF{4OsaFD%3o>GoId}ogIX&#o{9l${M4uzh9>A9f*SzPE0^`gRS@u_0E zaLGA@kJ2OJqOP+fO7D~1BhZ$BM^9Om`*)@OnPZr%a<_n{+I+qaKAR6Zu-)iRd0e;l z1FmM63NrGC1Qr*CjnK(>*{YsFLRu{oC?_<_a z{B(oWTb^p_!R>d+&ElOn2W^(N)-rB7$+y6sV@utQAVBOrh{P)(*E>{iIqiHH%nscL$@9=tD|`F)N>$v$baBIPiQ zasM!t)xBV5zGn$zcI&>a{bINF7hUxV_t5yDC12i1?arc0zLF8)hlw`^tO*8nDQxzy ziG&dOX-11>l3oxy-+)@J@%-CyWX>>A$D;~^yUok^ zIv_XB?zwYO4TfCU?zyYOn^hGFC-Sy}H={+;oNKR)=2r%4F@z)|w(<`R>HXU$B2vZ7P8D zWDf?)V@(EpSFhz=j*;E<-VQr5qff+z=v((GUAdXyl4+vxzF8Co`0gnAyaYi0M~Az36~as9ENL+gm3PFiJO20 zw^7#nj>oqfIM(CJWrGC;M)WK>$l^M%u>1}cDs?g(9=lNdBRduOm8W`EMZJNX;f7z8 zJ9^pI&yn0^E$Pj1L7_(H0XQ%_POR<7I50QBZc|XGQDr_i!C{~lrw7)mTw3@moD06O z7_D9n?k?Fm1jbGCO)Z|6evjSd7I~nk%2{SJ4DPUFonB69Ua0S_sk7wp1`j9~gi7dRtZI>$<`rEPlLR!ZiY3{k*YR~4b$ui%1xd_rzl)0u4K#4?2;L$DQ5 z%9}|{_kOp=)x631Y56|3U%%-L_IB^&s)(2*HzN?ZW9JVNSKdG0&vhUO(7WZvs8CMX z$EezSAGa~yv{^#lq36*Ie;LESiAP$%Ra+vEP=7nUyfMeo{NX^Rj;fuDuFQ`?E(gC5 z_zm8c7bA{oBIO%W|S%M;@jJ z5{Z?%{MX)tcyUS=JNr_WRFegcudxy%zK!-ab)($l7Bf~YYf`i8T_ALy)=XmHY03;PyEG@0gl?Rc%N3~jp2x_+sE zX7PtmhMdBh6XhV0(ocr_&1pwUmIOzaYX0Qyy; zd#@Bt3TXoe#+hf_16RuDf^DrRSjIy2E=`Qz82$dJGHVHoz^2n}v&D9Ls3PVEmSW1j z*QrD_xM#B zTuVbvf$93RKooVDm)mta@KPrtWdt*-L=5D>Zz_kf(rxX0y`*J2%GjU}Hkw@&^xt0~ z`w5m5@3HPmR>Sn&-!uCAg#w&1cpW*ep{)1zS?g3WvBou|(v88l9_>rg2pzBngis8U zd!`{im{2BP$bHQ}zq-+2Os0q_HS|SF5hV#25I?)P1Kb+;K1UR`zGTVsD^oGS?=fT` z#{#2j?jR9Pr$IxBsu@!ICEkdP!`+xU;qNs!xeBHSX9D@~7jwu|<`c6Z+S(c%`@L zk5?|R9RVF1tGEgkvEE+GMk^=84!Vnv^#vPPr&}fni8@cxgFan`|7ziGn@YO`D?Qy- z>~^=L39xPFEY$yMNxkA*uYPW-u!tX!AogfT{3N-u5fj$*0z*2AL!S^xo|H&`II-*g z%xsrV51VC}4+QIf9J79W_Zb}7w%adB+({pIj+S5B`#v`pvwEQit+htH8uHJ8nD?P+ zxAX|s*gR6BqsfM-F%9Azj|T!7%SxBXd=uZDnke2!!M*~xFsa^wPW63H9^?ei?hot! zHvOr!w7~Qn61(4!8)%i1O(gh9*<%_~>I#t$K#pPgkY%t{pFGgLo9B5~mc`--Ey zivw%9Z+F$~{^9sPUAurh`&oVwdbwxv{L%V*-)J!?!TP<8sYzOa$-s&#&6(Kw+GPYT z(z&E8>6jQ_j=ji_(1Q?3A8Roi?{!{PyW7DW+Gf|5g3-qRHteKrlqYrB5$Pnt(wkfu zNJ)rgnbTT4znNR~AN!D9&6r?^|Jm9wU5iILks=%zq;w88k;#pJ5|Wg&eTPf$6gxj2 znuh;#r%miRkNLT~K8j~K#J)RtbtTAVB%=FV2G-j69D3PKPT$@ZvPQ#ZE6))v9V;4X zojK17N#KvP{?!>uCreU$Z4a7->4vg(vauYm&d{>%GVR>@18DzP<{iOF6PI zed0|OKS_Gil$d$ZM65`3_2pt;*OAlNL2ddZY-mKzktlE>rbxb6dXP2Z_a!mwX7eio zh_;W;{ZSeiQ$&qin5ec=VV$E-#@t5wv&1oHfE-uFB!u@D$&tnh+`rY>4mWx~ztiUGrP&V> z+?WEF4*Dp4;ZY1Pq;Jg&P&&{8V&%cIO1%}4_CI5d0R1^_%#MtQYi+yt&=~Z~WKvIs=B*!hrXDF`{@Q!N|vaXtD^^*bayWrbV#+OTw z((`>cV&=%$I)1TtW;yRLyy zN?L=;w#~G7)h?DSr)+s)WzXCpl9ouaZ0o2zy1H-vP!=Q>vaGD$kN9>wV(j*%8}a)^ z$hk04knumAkep8`;pbh1pMPH4KL(3f923FPlmWH28v2)inN z59D-48fiH4PEh1i|Tit}{;tLm^RJIR^UGMDr$5rxj&tW3apOIQqcv3jE z3D()dyY1QPM(yf?Sy^wkye1uAd9;Qb)oJkCiaAuXq~ z82+ko$GbH)Kf_%(<95!u)r`941M3KzAJ%XM>w=bx3+IJnYs#^6!hkB2>WT&~)QPO) z&;Xm+cR9R+=%FnX5eGCL-4X$*PGR@KIG(iON+JlQ^q5ypY1kL^l&dtY2V`j-M|6y5 z9|#)6FZ~pG*=~w})i@Ew;aqfY#=KVmMcr|ZKk`2!LQ!@U;NcnD{mJ4bKhOx`tBzFf zcMR3Tohh)oy$?lgxPCOF_fZj`L9*7i^oUsI%Hpc|+MEP*9lRD+E2{B`Le?sQ0p* zm4$RX3GY?do0oV_x%#5-)`LsNogmFqdGM1Km~Q?2B!vLQHVIx!1v+Gy&DPrP5 z+FN-*!T}RRW6DQ*`(i_`S;SKa`^Uz@jE)MIaY~@<7E@1zvf*k4-!)WD8PN%J|s#pP6-`7^FoRlIcReV1s* z>h#4qt&fnU4Q^)IPqb>sB{;S+(@jYW%DF@uZ0Cz+`udhtep$iIOwUUN-ftvIiTJXF zQUmD=SAsckgU2Qkdg7zI`@RD_qa>PBG_AKW04Ta}Eo==NIcs0aE*5C&vG;UhC(p%B zA*02yd)xBkoFGjc8nt6tOM5rFWL!;NpK`Se2x$MpC3&q86KC%v9MQRqu6N2?nw3Ti z=l)2s2f0LDfutQ5r-j0SUk3dUSx$}3_VNM@)Sc&_t}QLj?<`JlMwX=~id^*}>FP}U z9Nh5&HwkN8RL17qXX5>mAS+CHW@QjEJJ6Jv>!O_t0ca+~RT!b`EMbU?(sN4H(;_awPrIM!ppSZs;;tmBSnvJ35id%~a%RBxk)Ygp z@aV+iUXRVN-!6X4f?%v=!3d)eih_ zC(!uMC%zb!Ll$HZY||8-5EZqxiXV`<#FxFoR#G#6mtOk0+*dR)e~5rh95i0Vb%<~; zM|AV$!Y|z(D-X!EJGhDy83ZTdAl)26;kuSc%xvTIid>3}L zW(Ge$Z<3=yDmI}BV*_FIHH)l%r?y}YJ|3-s&B_BNo7XapH!o|l#a|m4b$HyBvMAnU>V7xcyfr9@m#HG>8v4Ucw z&4(w4 zS8NZm6BPeC7(`&_HprCFW3L8g6mu_oHX(7=E+t@(^L!vy6fJceeya%l90Fd+-O+TP zW$#Ek9CM?$>RbCoRBBotXcQEOmXSgnnc)+8Cs%6W%ii##uHU4Yj!KVCI=@lwWO3Wj z$D65dvG{z-gjV;PknBdDUh@mW)ONbku>=D~eYv^4wF_-6nA21d*B|kz@-ei2M6JfY zevs|~?ew=^MR8&nv2``?0lYEIuY!*C%%MZn@&?2V=* zNC}XCqtlxBYM^oi&AJV_<5hIiAU$juZ8bD-5e0TaUH|t(=IG@ioG8SF5i6+|g4AdM zVx&4*yWL3WV8P$_sA98j{isxj?wtrE>WdbI-lvi(r#FLd;KA}_?k=GH z(RY(xk-iu;t9cWe!9Y;^_qXyxM8Rd=xju6SK{c-*M6t-9BBUvVjrfb8O;H)U=`*7Z z)ZEmbyNH&3hJj?Ml1b+jGhat|)o9_7lM^*$O-AYHE6zOX4&o`*j=7{|An(;ed&>`# z6!aqj>LpRQlf+EfB=VNhQLzeU5W_b~g~_E|QDupdX#enVBJEeqo>0pyvA_Nb#Z*=% zwQU|C=fp}`@GGFz(R_6Bi6!YpQgq=?AWAURtd6S85LRnBYSqGwV$E`&lhiK_mFe2$ zc51Q2+pqIrJ;kvf$vYs+73awxC2b~vZC*5jJ2W^le5}K>s17$8aKkN2aB=TR6=w58 zTdxmr?WFB~(bSNzqLf?jtsRkM)`>5hx7>^evFswxgNCSU}}fQ{m<6RDa2o0i7eclJgqD=Y@fFLI3rGG3}{ zYOJ-1f6vv5$ctIsC1sd1y%M1Kx*Ds|!)Yh(qpUH*;z{nwg`o?Uw8yG94_*yDg~ihr z?bJDn#_(TZr$NjweVZATmzf$rm1t|&-f#*Ce6wk=E2o=Z_d&!h&fm$1;G#BO&QPEV zo?;Q!v#v0Yk}ENLhl})1phAXj>C0g{;gN^tND8jJ65Luclx|dRl}6aQYJer(T@_N! z80AZkj)=x7T~L_kUdJ#(5Fv5YZfAldL#WZx|L+<;TwqXD^d0AXc0_mR7`$dV-yILb zjk>vkvzeF|9$*!DyS}Ex4)^oyw{YMX7N>L@lip~m(d;K|#aJ_v8%ue1YDF%9Z)2k8 z@OX?{>oE=6JR<+8P~4nDSXrPpoc9o*HM+@^&R2?3(0M6ZFM((R74v3_ewor0nMiNAxj_840ZZZ){o9v#38CgDhl^kUr&A_83 z&s%?)i+d!g(}ZagIk_B6@? zG32sZ*Z^R>nE9t}LKdAUW+eaCq-tKJcps>nCzI?!%k~6DgA_THOrG>cU0X8QG7ay4 zb~Nq+OS2v$T`zCaMdCDROVwCA&cp45QPA=G0M*NB$@|LWHRyq#C*kR;=4vL)d4B?c z|KQ4n3XMy9<7L}ijr0%q;C; z;o@!U)GOlG!B9(4R#IBY6^nT!o%-h?>px#c;SrrvXyZlc9<6=n?CH&t5v7LC^Jt7P z2{y#5-3YIYX-lU>5ZXic~I<#bkYF<||?l72L;x*&5oS<-NPiajZGjwLpqN4Jx>ir@j!kq1X>|q*0@CIGP325q!u=G&KBDBp*ZZa7#;lyXDyX ztKI}Nt43peUs$6{*q}}3AN(T+em9h*1;_D6_+5i0T*gHiSy6H%!netUWK|CPCuaJd zLF}~o3*5||IVQlj*XUGZQ2?#U6MEOdhRQ$IU4VZz1szyG2<`?@sY;;w0t+2G%$|-%6E}I14PO z-1lbdViG2K_*ixU!XUR6NCR=6CV5g-aDhma9N#=Wo*Z37MYkLg9$dd>ziG=tVm#!V zTvp+`ZsRd|TP`mRYjJroHYt63uvkTQW)oZ}IYz8=LjqrqHjRUPZm98F^77GRyaJ{|63v*motrX4 zZn44Sq?4eDaXV=lHp;RBgOcki{+=&<7hfa^>;yUSJqOO}`0qP!so!Gh6|YZ};K*$0 zP{$CcbCN45heeV{pZ?68LXnGrM0V`?Bv2M5B=nAhYL z8sH@pOu`99O5YJL4W%n3i(g%S?|J*dH>2V$FV@!-C=Dl-0nJoR`uy|~KX{Z6jKl|7 zV9Qa-NG^6Pr4=DI*dsPEeC#Tt z#V(dTf;l$0|dZfl)02Dl6ezqB*dXO$((-Sko)&t<+`s>R&eb znZX_n^>lxv$~>#-9lMbtGXYS+#U|%C7C63I_97jXTqzys@avm3W|Y#=XQ*5J7DucT z58KYFvKtHf$NI((_Yd(2Yljcd4}9Ghj+UMQp^3o)y#@~t0i zx9X+bA+~J0veU9sk3nNJd*yxCD9>QVlkt5Kbf-P6;n`*})~%T<-&AOjqHnJD-K=xU z>{sYPcmy8f0Ow2>{7d_Vn{Bq*+)ZcD=v$_VUu!XM9IYet9a1~^3z13}Q@>^iN5ri} zC?>#h7Ik8)Tj54D;o_Mf&rFIzGTcg99Fyj&yUyb*uHi}Y-fR=o&*hc3jJ*0fuP z*Y?~Kp7#XcKi&1rWClA?=O5~n#KHcQ>iY?=VmwijrI3bIxtD^3bHpz630uV4Z`B$Vcw_aq7k)~o!UCF);wFe3L5#QVpy zje@x4%>#(@omsKcA#fdkCCLOmB*U^9IukvnMF?7cxGzkicAeeEyKbYEBJ4I-IDF_| zA-;Wn+9Gbu{#oa^%#8FVi4b>V+Z`Qxi!$*r+8~YlPIifV*;K zB6lZpu5#&7>qX_fD0%@Gz|=^swiJmIQ2BKYT+t%W;uc-8Q|c?o@h6kQLgvBca*Z!1 z{+p%mrCb1EEzw9$e%wkWN(jXU{qH|lKlSo)Pw!9oh)WJi3$$%l@*^8r0E~lgtL-Da ze)wcXn6;i)J43g*B9JK!Qm5MIc&F#$4R@#M?zd_>&kvD2oCChQk$(4D&z}}LlZ#e; zGwMSHC5Z1autD*l{OjC#mSRl!@86wh@w%wC)B1tvZ6Mq~vjMD?DXRm~r{1_1!%Q#u zrL6%WfsVEFOiWeF97~$L!y+qlpof^`^e;JW&S;HKK96TScfsuqQ@na_RQUg?r}3FJ zST;70#?4n~q2{Fo%wk(5B8`2uohT=KFK}sENgm+TTwsBGNKNx+H=enk#aJOLmkxI{ zX+aBH89yeAK#n%zNtMK3@$%oYuBV-@Lkxjws1;FYC}A4u!(Ap)hxhx^iR&ZIpQ&zm zzy7@u4Qbta#Lw|LSN?Ey_J*6qMy%OQzqD9_%U7QQ*oYfnYRz8(@6@JFq-S z0XFaZBctj)Ss6&e9A}6Bmr-n_Mv$#h>yUNfyW$)$qxm56hsMJo0`qbVWjE_sh_lhT z`?a5y94*SJk@pY8+wVS{X&$jb?JGsH5)1cR_ZHdB+F=NHs}v%S+YW7)Yw^ExQF7%~ z)P|=x>pr1>8w|7+As3|1o#y+(43fU|ACm^ipJf{oVo$rB9k9jxNC>oDW7bnm5M^tc zg%91a%n#jXj6yy!1#!F|7kU1x@!_P}8zJvSUq8)jk!k~de?8qM2h(EsBb&26SmTZy zPmzsl$s3?*G;xK5Om)R-AE^g%aQJvV7vB5vYI^5iQf<#OA%*`g+Nw%)JVohhZEv?W~iZg)2e< zj8*WS%CCla#4C5yKE&7wY_WsSwZ$1MOW{-To9TjK@+s3Ys1Db=2~$E6&7xdU$8lNt zBy(XHuuSdVxn!QCL?`6eGU?SrI}`^e#vFoeQXI{nnG1}BI%O5 zbXJA_)=Mt|$G5V^z&%N8XSoIbOv0^;2wmeccI~78y=r6fq30r;(jPO`&|M1!Czq_C zkkT#tz!h-Kvk96sJJ~xQR99q!JIMAXX=;*68Ckckvy~1{66DbrQ`|E62Y1{5d9^Gf zDKunk-%?J@NLqK09)%SvhV~y>S(8O6#L#;vw>^g{IqS(BJXd0r+^SSgA|Xi1Y*d zFPC-wx(ye6HEBI|GP&?(`c`Vs**z%p0n(O={nK%R?k>c$JuaPb=GezJ*rG}@Nnf)+ zcZ(AkNiqoX$o-D|+s#CT%q6Txew(fQt{^vJI2BMa)_Pkz?8 zKV$Ak&^xKtY>$C~1jL$~t_*Bu9*KOaOh-6Ta2cPq6+Z@Uv_t?5CWhZe# zRdhV7d#hVxl_%PfkmZ zLcWvUzh}#AeooYOhf!>F4nPz+&@-R>o$3)-8`tXGcbXy&0ola^yjgA&%h9EcYyUp4Nb1 zp`fG3{5M03?+%uBtuJp)`!9CBs~;@+p2L_0nnSL~c0>d6FMNnC6dh*29=%uXCJ<+H zzpC-6Ae`$m+|L&1p&MH0JK^o=D-dc9%W2q6(o5VjMW1ECEBbRJn#uatC6tM^vQ;_x z?`xMf(r7dYI_TI3&5Psh6Hmh5SfklF2G&V4E%r=&7kzf)`gp(h{{#yq^x9~6_SyeA zQ8R7P(`blXRvwyfzIkBltv81?)>su5TWnDnG-x2qNWg$c35*^+23~&oHMsx&N5poO z+p^0n1@p{1ckVcU=D*JiNAD`DtY|ec=-oSC^X=WM7yN$1bu@6;5yt?JKKittKE0CT zti9IiK&vn^1 zxOOWnw>Zo>$82T-M=cike*Wb*@X4oN$wetG!o@+fXZIek?mE8_p@Q7`IQN{h!+-&O zMO^q#KaYb?J{t`$zVbFaJnA|4YRvbVl_HI_!B|U1+;LfCAAS5eoPPFY>DQtCIg|g7 zpD-SdIOcQ_*16^88{{5im6eu(K?7%npMM@p9MqNcdT5P2y$(w(zA$XE(K^X{mWM?a zSwOt6rKLqgBK`Q|&+yvoZ^QpycoiOh;=eF`S|uyDpvmr>cb>UmnPrw#BQ`Uz-hJnN z5e-EL-CT3d32UyoDlERlVlbe8KbSIQDvTU?FTDBYJFvil3&4^~E@sR0gpt4g`di64 z{wR*a$9~_wei>`VjAX1l`HXnofB^$w=+I4I?X`cK%#}r8VDf!=*x`f;6XC0`M#F2b zy(vPFUwY|P61-r8IX|cQ=O3)+^!)P|JGS|2fB*aSfR;Z{KY#PhxA5+}A2c+ecf!q)A`lp_H7KRT$8xA;NZ*QWMk&U*1c?Zt}H{W<=yItWWmin#MP5A=!N}xtn0nt7i|`eYat-$F9R*;H>>ikO73ppLz+-`}g(m>E~nQa}}b+r{!^Pzwta+ zdBtUOk8|rC55Vy!pAU9j9A-rd4I0=Vo__4MWD)J@$TZ)=JMVrZ9wSCRlmfa^u(8!B zUJ1Cbv-WCm$Y1t?6_#JR)cxZjg16juH(Ytu%`kb=WEp-*%K9chZ@bMWm>>hjU$#DDaBVo_Ic7fxM`&;hu4m|L19qDrKy|)P; zmd`j?ud&94aL_^f!rpuD20eTBtonW5d+!4{Z>Zv2Gvk@=+jkbY^2!TgyX|(VKY(}MNgm0<_Vc*$ zCUcf*8`^cy3Vr+chv6q537c)QUQPFJ;J^WJ;QqVAHp4c9BaS=~UU}sWptaqR)YL4R zO#HiSzWGLQ<{2j?JHFoDmvNR}2H&V3s%S3pq^uDmZpa0EpDn%gl5pL1m%=sI+yrNy zc^*_MuGG@L7}LngO9WlW=9_O`xcK68p?ml4GjYRhcF!lo6<7SbCNS61Nbg>-=%NeP zw1BkVE=V;tE~CdI+<4Q7!dGp+>3Wi-n$Gixqy(*K-!=obAGT@Xac;WxUdfE z!je(=$!Rp0xMV-JG=J$)yffA-(uPrD3*6_#64Fnb-{p}V%J2oxGxDnVQq z6H6V!{rmQY8?XBpEVA$dGyUE8xtx2>$*{)it7r#0Vs(bT$RZ0R@B3$A9iLPpnNYoW z96(K>gEM1WZM7Nv^{)qZ$Y%vWZxGvZ%gx~6gZ~Ug5ifbbs`Pz6k$cx^Gg)a6O+`Z{!7TyPZ4-801R(d^Hwq?;sD}GS~`>eDbOPm3lAt9Sq%U{RG?(h7q@30)I#TrR4m!^S*IUYwT4d3pnmQt$u6-UK*>CXefRlOsp~ss%9N605IZcc zoBN$sT4_i?Z{JvGwl6ew>Qop#`fK4eFS0`qJpexa^mDlHz6aYNS0L=N%Z{+{!V6Y? zHFpBN_~OeV%4N`?L9lwlq-L3AmYP0q+Vp9nJ`FWqSA$1D_^eWi;oEP&t3vr{)@QX- zNaMdBd*bQjxZfwAJ*W3&D-BsD0nLTrqffp7lljOrpzW~j7KI;k<1KfK*BMm`&fuk&UWd2e{ScO0 zrZx$nXU`t6%F4@$2e&u@^Ecjl5AOcYBk-T(oTf~kYKp6|NM7!=AlioOuLT=!@Vly? z!H@aXS7XGkW$t_XE)#pa-66Wi# zzbSTroE$B;+|sbnLdEln?}q;V_akBZ?RQNR;mF+J$Cz`@Idb1SY0@O|9-m`?&(Q$r zoy>TM;O@Ks8ScLORuOqpWM`dqN-`M6z^kvmUef^NEOW2Db}M{cYilcZvf%KjRwty~yV_R6bo!F%t0 z2z_Vi1Ap3O+rsOA`dOF2)z{u8Ku^Wb-!<|9xcthS;hM|O&i$Nq)?O7BUvvTZ@T1Rk z-_ySzet(qML8)zSG9FeMvaEO<{?~orybG>?+wZ&=Dy`C=prW>tpAS8F?<$03 z8eDSe)o{}-cbbA%$$NkM+g0JD6OMw#7tbduf81$1xZq!x!uWCH+l?ttoHVKQMzPi! zDNiH>;pCG~gD<`qZ4J0J*IXS=JMAP`aKU^@YUP!OBrJUexbVVDlgB0cvw5W2gAd-7 z`#v7AGko}2CIpu;@-slACm1!9qXox&8Fi!h>I`2Jo){g#EFa>LCd#PK)&{qICXQl4FS(baI})iCeyF+~2*BfyKMULJus6JwjG6Yb->tnGTz2spsn0-`8(43h zHH&wQ=UfD@zxI|$(9%v;k-?bWInI{=IGoAr z>r?}~HlJzUmu=@5B$rOd6!(ci0*pc<>1w@4fZVjRkwmWoviblk8-s3vgb(Ah~At*$2S~Z$CX#V`J&1 z7880p-(@hybe85vAAJtPw%aq=A*>5K|8ZMbd#%-GIu(5N)mDPN_t**k?~3ciH6Jo$ zncUa%Vpq4`cDHOi&C&)SwD~s^Re41AEAf!d^19<7>SK;NCUK6@7f(a8XyPduA z%Io3u)6cWh`ehn?xiS;A+m%;dr?0=77qlg zgPoOV1U3CO z{`|<6mtTHef?Oo;TSSVgq1;ZV*MHbHy9!x}`F2?~;4hb0cm}2_#AbFqrc4B5?T_hC zKmDA;$nBc#itzB?c|buPp!4^02R$$AQLE1&AIyV>{wn}>nJtw8_jAtq7uq)C!Y<&w%rq!8?ruZx5Hj=%J6gHp8Fma9f`^qm!NoL6W@LG>64$p^X+%vNqHrK z?I>lNyiQDd|09iiX%{|py4E?3!AtJq&OP^{^yN`xk5gT!jkUW3!51PmM+}hq=1OYc zeDhu5@%r`aXODVT1{<0a%o`NY%d>O^?0jq=am3$6EtZ)#w4zpA<6A(wsL<^lV_+>b z{O2;Uz^wK)O}>Us4%hjUX;D+8GRfxDkuJ=2*IoC(um`eZ?Xf7 zy!-yDAGh*KE6938`MaT>J$mXSE)8l@YGG1_(I>;)^JnD+Y)w3VgqL4=Jp*J$yRQ$( z@dcaD5qp049F+39R7otWR4Uea*Cxw07nnDxE6{{8RQfni&3mV0O(XZ_4G|I-T#4~yhQ)be&fza-ba8e=QEhU)81oi^2C zv>X(B_2~&qEx9NxyUY@>Y_g*m{ngiS@uk<8F(kdqq5`CJXK>l+@FV^KbImm;tXbeF zpMCb(#J@lN^fSyqzrTzD4@X36tT4-ULhm1!AmRkb-hKCdXl<=H%_++1F0a1!hT~*s zS^_H?ukB%rFTSWacUMx(j&N4BG)5XbUhAx2USJP^-f00ny-C#B!f!YS;@x-OAMU>U z7Cp(@VmzFR7uNC_Pa5LV&2!KFFG~m=TYmXvVZjCGFNw!YGe@_Xbat1}-GlzLk+P|` zUV1Sgk`0rEy>7VS_FO>ccG~dcpnpMy#+z;)0kMoYQJO8UUC&D@`0Ue9zkuy`+>7|{ z(p>}U-lohN1gZ=$(`AIsfZ#IFYOAgU%PqH*(8u{gOtV<|;&IJ&w}{YJMlP2P9chgD ztFOLM232Hwj1yteMHYf-)20hxeD$?A z;f>ed6#u&K&$CZI`Ao`a9>Rd`H*H^V=b73R(DRHZ39YwYei9bHH>rH%E(m4F4YLSe zvw(URkX&qSjwcfF812RNB{*}4<_?(Y9peucuf@m~Ni{@4x?n*tX{xuLYlH)2B~|JMX;5(((DH6diBnm6j)ZJkzc5 zm;>g6=bHz9x8`ah6`s)!Nu(DckBdL{m_uO8p&KVGaG^{|f>{UjhlBRt)4C6gER2k# zxz98AT!mL}{P=W7_tJ~6=1;`r^N&6H5K|L{ju}i{(tKyM?Y2V&n5S|?50PB6_g=fg zg%_R$|9$2mIQXD_OWjc(Rior`TH(H==r>cg>#l!D7SM$V;qkT7F!ixx#} zjW^uZrh9b%1EWOahO`ThnU)Vc^jP8hhM#h*Z0-@;gj_!Q^y&c@|NBh1?%Ips)t4TF z$41=+|2qFv*k`YuVbxVv6pl&$EuMd&x<6&V{dR}lciYj~V%hh7rDT!WYOCT3lJCFw zkvImA>n=*l+IZvj;lzI&DWYjmmibj(mN=NNwdU$@;t59;e%Ak9c)8T|=Ormz9}CM6 ziTYGD^YLA_2(OJrokm2+WClE@7{f2 z)>->EJoYuECVc$yrxS)e9EJ}+q2{B1_ucpK^wZD6y6di;`vn{K`x zZ9rM#<4mi!h^Y!NsRr@DgQJq`cvK-9_scDJlCVz2KoMSj<&A{-e*{Y{wYc|vE3B{# z+;H8+aO6=Z!RI1nTsKCAWtLeAhM)R(E0%r11qX}A7MrJW^XHs<30!vhb@0UF&j<;w zTG``&9Djs(?`^k_gx6nx2fqK|2az|h@IniS^0T|`x?^F1Hvavddggg?nmm>NKmU0E zw%cy20`|Hith3IVa7VIxdF!pW1(Tk4p1G6H+E8@&DRTbtOxaiX4yOeER^o~$B7{mU z^_3tNSYU8+EpCSEuDb>9zyBflUvkngf?CB3T-guj6 zNwwd8d)2g1aJ%TD|0@J^Zk!36b=IlvARlGzl~-PaVZ(+NzH^H$HWm-p=9_mO7|pfT zni55k#KxF6&PXQKZMWYA2kgIh=|k~0UmP42`xhlyNFU6z&%Fq)y5e8C@8>eZqmPV$ z*WY*>zDz)!Gd^Apc8MhxE&N>WK)e3N+XeVfo-!G(xbg-#@`!_~p6|*luOJ?6v4HIRcWm?QW+2JLOU4*UN5?&;-(O*gCx=G;y``7D?$icO{GdgWEuCoK5g zy4@}Ar#a)Se~IrE$?wJ&(&7I+yWKyF%MbV6`;a~KtBoae$CiIkbhhkC?g+gwS-k9c za%1=0^FW)9yTS^~ik)U7msxLGrzpV)2h<^xq{C}Q@J6)d78av9o^;abqTgZ1?Kn`g z2VU2Nvy?Ug`ugjbX^SP?VKS-6j(G;1fb#X2Z{V@V%cbOV?e^aLAJRvUv<^n2ctF%F zc_U%q?Pa&#HWKcCaFi9|$K&4j+wU;=;>*$Pe$Tjn=5K#HL4;T1(xJoIq&+5bBnrz%7tC$m8nThC(*k;4 zv7Fix2;3E%>B2|!!3PPCQRkXk*&ELvyteZ0yYB}deDF~RTn{y9BYl0&Jo5th?6c2n zJ{B)7mUaTdl2g@=TTd4jl93xRxar1n0Cm^M`$UcHNO$}}{WBwVXQoV^47=^Ve`zOQ zk#Rlx#FNfQ)5%qh5^3Ll_XBLd!yY0HzP)Vnlqv9+zZ?Y*J^Yxc$6|IgjC-ub&zuZg zbkXHA{aM_>ztc{8!MO1gKvaZatR$l*4sK@=p!e30iC-Vod7-d_5B{6*6S$eC^j!Jt zjy&?XHtk@!?BZUZ0O)mN-DRt(xom@33@2&Y8J^b*aFlyA}C4t-qQ*M(cO@y6y-lyhW0(Tg39Xh@JWz$aGbZJI#9^vU{ zo|82-itT1uG9V2wKs%9D$P$sxJm*P9yY03goPWWk!qHW$aTfQtqfScL{wWD#HtdyF z+T)L(e*6h`+Ig=8^iQAZCI?SF^(+h>`bT(V)D!afkz6>lp{M7acMkrb`?DK z*pqPFaYq(@A3kyE0!0hy@h$du-+c$ehW#;FjCK`GbmuOJiRL?zC!Tl)uD$MNc;SVY z#Bq40VOmVge=9h^(PTo=yhx>?lWV{qS6_Vt?7Q#oxghZ7oA1D9pMMUz%v6+e3LzD! z!h))Wo6O4O-~ZqLz6?f;_>X8RGHmP3t47p(`q>vEnuKfn<8^qROw3`Xh6~Eih)dZrFOu&0x!|HiOlZ$z7%(7bgLaKK7Kj|8KqZ9;pkHI*@H5qOMtbBcFZt zdDwWPVX*bqTO^C~4!Ma(%6M2FkKe!PrrU&^Lvr*1LWyn=o%zQ8^Pqr)H0gz2 zKl>Wk-zmze+kv(K5Ie^v3%Pn#OY$9;TW%RxZrP<_{sjg@_wL;U81wJ-+2>!vyYGAe zpMUWsVZ=;GLIg|A@#E|`OA#$adY<9Aj&ME&*PF2|Gd$o$G}@} zzYn9o8f|LUTjK^T9Jj9<89(Huyp`O_E3XKPF19etHfWIcJ8(xJPo&}U9^Y+bYvYr+ zYBYVoDZr)oGcpUC<4`o`1Ud5#3+AhN%>xhIm0YtWbA2d7hOD8-HP0znb=6hGxSegb zfzYd0FHxrBi!Z)_S6_Wo)Oq)pyS2}m&JEu=^Uqmv#UY}s11ALhbNC|k-FH6-$N0PN zzL(5XY*T&p&SLMs{}StzfNl>e$N)#N43Y(7aDc%(sWRJ67sy#gfAj%++oUAOY#ZIb zQBcc*C_1|v^>36dz7!SLaAH!4)7Mid25!oUZ$Ysa30r&hfnAmzq6AX={>16OnmS_6 zZieYHu==w!<@8X`TyQm?W8c%PHf?q)lte@sACLt)xN_m#VEQCqdz7WWk&c`n+pFKReQ3PMa0bwWw^Hu9U?^ zSb#i*LqwZi(nb8 zZsxP=T$Wz#{8^RHW}bnYbABvu(ji$844RojfOd60y_RIyE7)M}W1o?dKRn+Lne)}$ z?;m+TvuLbX<3N?B)xfUKQ+`fH-t#c$qWh-FBeTRa#^~y*wqa@0Yk&2g>Og3NxtsOr zdtWv3!@RodR2lPCkE+#hiVpx4XS0hraX{s)EVutPq-|S8C0WYiJxb7CR zcmfv<@V0(WpHCp~v)ps`f3^$YEFb(G3R-wQWyM0I3|K1^NSat1_NXNrbiSX3GpV$r zK|NB^0W*NBP9t_EY(&2YE4YR%Babnnts^+qOyp-%f@hrvLIzkU{Kb|D_k zUM^IOo)`T*bQrR{2dV$U95a=o$USAA?S)u|^0qEUd2FPdUYduL=+}dp2N)qw!uN+>nx~39dCV|V zB7JCriePQngd><~(hooYR0 z+k2j5$9TS8UB({;g_?Ew@mh8+`{zdcd`nTxfA;of`qrgEf?44yfk=d_hD{fn1PPt%yv6o`%)AeI=BX zf&RMg%sox?XiDT501ilF_B^eR$pO#2^G7Io{jdaBW6Af`AJk2FqkTJl(K-GQK^CT7 z0Q6w>2li?|vmE5E!sfByGizn|)wJ5ezxy-}tiJom9GiL7r2H>awohmM`gyh=^UVn} z^}X5y<}6@&0Q6v;5%`NFfoie&R1{}bK%ZrqK9<14s=I`e$IRKv+Yv2LuqwIE=%NM^ zoxjo_t&!Azc_P6Apa<*3%)Tr*RddP~F@MYf*)pd?7m~8=*RaH@QSH5*nSIwTy%gn% z_yIhCc>wfaU4-rDp|OwoV->nxZ3p24WgmNHHJoQxvU=r-1Pg#3tP3pwGXt>I7rGBBkOwdifF3MZDZp+^*j{If9BF(|w}Cs@uVqoV1i_jM?1KdhfF3MZuwcOg zpa%;UELgAr=)r;o3l^*$0e!dCg8&&USg>GCV65%EFoOjP7A#n2vi}!g0FEE+GcSN? Q1^@s607*qoM6N<$f>N!hyZ`_I diff --git a/src/tests/testdata/DebuggerStatement/app/App_Resources/iOS/Info.plist b/src/tests/testdata/DebuggerStatement/app/App_Resources/iOS/Info.plist deleted file mode 100644 index 0a8e1eb..0000000 --- a/src/tests/testdata/DebuggerStatement/app/App_Resources/iOS/Info.plist +++ /dev/null @@ -1,66 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - icon.png - CFBundleIcons - - CFBundlePrimaryIcon - - CFBundleIconFiles - - icon-40 - icon-60 - icon-72 - icon-76 - Icon-Small - Icon-Small-50 - - UIPrerenderedIcon - - - - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiresFullScreen - - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/src/tests/testdata/DebuggerStatement/app/app.js b/src/tests/testdata/DebuggerStatement/app/app.js deleted file mode 100644 index efaf44a..0000000 --- a/src/tests/testdata/DebuggerStatement/app/app.js +++ /dev/null @@ -1,2 +0,0 @@ -var application = require("application"); -application.start({ moduleName: "main-page" }); diff --git a/src/tests/testdata/DebuggerStatement/app/debuggerStatement.js b/src/tests/testdata/DebuggerStatement/app/debuggerStatement.js deleted file mode 100644 index 419cedc..0000000 --- a/src/tests/testdata/DebuggerStatement/app/debuggerStatement.js +++ /dev/null @@ -1,5 +0,0 @@ -function log(message) { - console.log(message); -} -debugger; -log('Debugger statment test'); \ No newline at end of file diff --git a/src/tests/testdata/DebuggerStatement/app/main-page.js b/src/tests/testdata/DebuggerStatement/app/main-page.js deleted file mode 100644 index 82c042f..0000000 --- a/src/tests/testdata/DebuggerStatement/app/main-page.js +++ /dev/null @@ -1,6 +0,0 @@ -var ds = require('./debuggerStatement'); - -function onNavigatingTo(args) { - var page = args.object; -} -exports.onNavigatingTo = onNavigatingTo; \ No newline at end of file diff --git a/src/tests/testdata/DebuggerStatement/app/main-page.xml b/src/tests/testdata/DebuggerStatement/app/main-page.xml deleted file mode 100644 index d7c0b3f..0000000 --- a/src/tests/testdata/DebuggerStatement/app/main-page.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -