diff --git a/.gitignore b/.gitignore index 8214f761..3ac848d7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ npm-debug.log .DS_Store .vscode src +definitions diff --git a/package.json b/package.json index eb514d6b..be57205c 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,12 @@ }, "devDependencies": { "@types/acorn": "^4.0.2", + "@types/async": "^2.0.40", "@types/estree": "0.0.35", + "@types/extend": "^2.0.30", + "@types/lodash": "^4.14.66", "@types/node": "^7.0.18", + "@types/semver": "^5.3.31", "@types/source-map": "^0.5.0", "changelog-maker": "^2.2.2", "clang-format": "^1.0.53", diff --git a/src.ts/agent/debug-assert.ts b/src.ts/agent/debug-assert.ts index 2faf18ff..c3dfaf94 100644 --- a/src.ts/agent/debug-assert.ts +++ b/src.ts/agent/debug-assert.ts @@ -16,7 +16,7 @@ import * as realAssert from 'assert'; -const nop = _ => _; +const nop = (_: any) => _; const fakeAssert: any = nop; fakeAssert.deepEqual = fakeAssert.deepStrictEqual = fakeAssert.doesNotThrow = fakeAssert.equal = fakeAssert.fail = fakeAssert.ifError = diff --git a/src.ts/agent/debuglet.ts b/src.ts/agent/debuglet.ts index 67610d7c..7cb7e713 100644 --- a/src.ts/agent/debuglet.ts +++ b/src.ts/agent/debuglet.ts @@ -14,16 +14,22 @@ * limitations under the License. */ -import * as common from '@google-cloud/common'; +import { Common } from '../types/common-types'; +const common: Common = require('@google-cloud/common'); + import * as crypto from 'crypto'; import {EventEmitter} from 'events'; import * as extend from 'extend'; import * as fs from 'fs'; -import * as metadata from 'gcp-metadata'; + +import { GcpMetadata } from '../types/gcp-metadata-types'; +const metadata: GcpMetadata = require('gcp-metadata'); + import * as _ from 'lodash'; import * as path from 'path'; import * as semver from 'semver'; import * as util from 'util'; +import * as http from 'http'; import {Controller} from '../controller'; import {Debuggee} from '../debuggee'; @@ -103,11 +109,11 @@ export class Debuglet extends EventEmitter { private debug_: Debug; private v8debug_: V8DebugApi|null; private running_: boolean; - private project_: string; + private project_: string|null; private fetcherActive_: boolean; private logger_: Logger; private debugletApi_: Controller; - private debuggee_: Debuggee; + private debuggee_: Debuggee|null; private activeBreakpointMap_: {[key: string]: Breakpoint}; private completedBreakpointMap_: {[key: string]: boolean}; @@ -195,28 +201,34 @@ export class Debuglet extends EventEmitter { start(): void { const that = this; fs.stat( - path.join(that.config_.workingDirectory, 'package.json'), + // TODO: Address the fact that `that.config_.workingDirectory` could + // be `null`. + path.join(that.config_.workingDirectory as string, 'package.json'), function(err1) { if (err1 && err1.code === 'ENOENT') { that.logger_.error('No package.json located in working directory.'); that.emit('initError', new Error('No package.json found.')); return; } - let id; + // TODO: Verify that it is fine for `id` to be undefined. + let id: string|undefined; if (process.env.GAE_MINOR_VERSION) { id = 'GAE-' + process.env.GAE_MINOR_VERSION; } + // TODO: Address the case when `that.config_.workingDirectory` is + // `null`. scanner.scan( - !id, that.config_.workingDirectory, /.js$|.map$/, - function(err2, fileStats, hash) { + !id, that.config_.workingDirectory as string, /.js$|.map$/, + function(err2: Error|null, fileStats?: scanner.ScanResults, hash?: string) { if (err2) { that.logger_.error('Error scanning the filesystem.', err2); that.emit('initError', err2); return; } - const jsStats = fileStats.selectStats(/.js$/); - const mapFiles = fileStats.selectFiles(/.map$/, process.cwd()); + // TODO: Handle the case where `fileStats` is `undefined`. + const jsStats = (fileStats as scanner.ScanResults).selectStats(/.js$/); + const mapFiles = (fileStats as scanner.ScanResults).selectFiles(/.map$/, process.cwd()); SourceMapper.create(mapFiles, function(err3, mapper) { if (err3) { that.logger_.error( @@ -226,13 +238,14 @@ export class Debuglet extends EventEmitter { } that.v8debug_ = v8debugapi.create( - that.logger_, that.config_, jsStats, mapper); + // TODO: Handle the case where `mapper` is `undefined`. + that.logger_, that.config_, jsStats, mapper as SourceMapper.SourceMapper); id = id || hash; that.logger_.info('Unique ID for this Application: ' + id); - that.getProjectId_(function(err4, project, onGCP) { + that.getProjectId_(function(err4: Error|null, project: string|undefined, onGCP?: boolean) { if (err4) { that.logger_.error( 'Unable to discover projectId. Please provide ' + @@ -261,10 +274,15 @@ export class Debuglet extends EventEmitter { // We can register as a debuggee now. that.logger_.debug('Starting debuggee, project', project); that.running_ = true; - that.project_ = project; + // TODO: Address the case where `project` is `undefined`. + that.project_ = project as string; that.debuggee_ = Debuglet.createDebuggee( - project, id, that.config_.serviceContext, - sourceContext, that.config_.description, null, onGCP); + // TODO: Address the case when `project` is `undefined`. + // TODO: Address the case when `id` is `undefined`. + project as string, id as string, + that.config_.serviceContext, + // TODO: Handle the case where `onGCP` is `undefined`. + sourceContext, that.config_.description, null, onGCP as boolean); that.scheduleRegistration_(0 /* immediately */); that.emit('started'); }); @@ -281,9 +299,9 @@ export class Debuglet extends EventEmitter { static createDebuggee( projectId: string, uid: string, serviceContext: - {service?: string, version?: string, minorVersion_?: string}, - sourceContext: {[key: string]: string}, description: string, - errorMessage: string, onGCP: boolean): Debuggee { + {service: string|null, version: string|null, minorVersion_: string|null}, + sourceContext: {[key: string]: string}, description: string|null, + errorMessage: string|null, onGCP: boolean): Debuggee { const cwd = process.cwd(); const mainScript = path.relative(cwd, process.argv[1]); @@ -352,7 +370,7 @@ export class Debuglet extends EventEmitter { * @private */ getProjectId_( - callback: (err?: Error, project?: string, onGCP?: boolean) => void): + callback: (err: Error|null, project?: string, onGCP?: boolean) => void): void { const that = this; @@ -361,7 +379,7 @@ export class Debuglet extends EventEmitter { // TODO: change this to getProjectId in the future. // TODO: Determine if it is expected that the second argument (which was // named `response`) is not used. - metadata.project('project-id', function(err, _, metadataProject) { + metadata.project('project-id', function(err: Error, _: http.ServerResponse, metadataProject: string) { // We should get an error if we are not on GCP. const onGCP = !err; @@ -405,7 +423,7 @@ export class Debuglet extends EventEmitter { scheduleRegistration_(seconds: number): void { const that = this; - function onError(err) { + function onError(err: Error) { that.logger_.error( 'Failed to re-register debuggee ' + that.project_ + ': ' + err); that.scheduleRegistration_(Math.min( @@ -418,7 +436,8 @@ export class Debuglet extends EventEmitter { return; } - that.debugletApi_.register(that.debuggee_, function(err, result) { + // TODO: Handle the case when `that.debuggee_` is null. + that.debugletApi_.register(that.debuggee_ as Debuggee, function(err: Error|null, result?: { debuggee: Debuggee; }) { if (err) { onError(err); return; @@ -427,16 +446,21 @@ export class Debuglet extends EventEmitter { // TODO: It appears that the Debuggee class never has an `isDisabled` // field set. Determine if this is a bug or if the following // code is not needed. - if (result.debuggee.isDisabled) { + // TODO: Handle the case when `result` is undefined. + if ((result as { debuggee: Debuggee}).debuggee.isDisabled) { // Server has disabled this debuggee / debug agent. onError(new Error('Disabled by the server')); that.emit('remotelyDisabled'); return; } - that.logger_.info('Registered as debuggee:', result.debuggee.id); - that.debuggee_.id = result.debuggee.id; - that.emit('registered', result.debuggee.id); + // TODO: Handle the case when `result` is undefined. + that.logger_.info('Registered as debuggee:', (result as {debuggee: Debuggee}).debuggee.id); + // TODO: Handle the case when `that.debuggee_` is null. + // TODO: Handle the case when `result` is undefined. + (that.debuggee_ as Debuggee).id = (result as {debuggee: Debuggee}).debuggee.id; + // TODO: Handle the case when `result` is undefined. + that.emit('registered', (result as {debuggee: Debuggee}).debuggee.id); if (!that.fetcherActive_) { that.scheduleBreakpointFetch_(0); } @@ -459,8 +483,9 @@ export class Debuglet extends EventEmitter { assert(that.fetcherActive_); that.logger_.info('Fetching breakpoints'); + // TODO: Address the case when `that.debuggee` is `null`. that.debugletApi_.listBreakpoints( - that.debuggee_, function(err, response, body) { + (that.debuggee_ as Debuggee), function(err: Error, response, body) { if (err) { that.logger_.error( 'Unable to fetch breakpoints – stopping fetcher', err); @@ -473,7 +498,8 @@ export class Debuglet extends EventEmitter { return; } - switch (response.statusCode) { + // TODO: Address the case where `response` is `undefined`. + switch ((response as http.ServerResponse).statusCode) { case 404: // Registration expired. Deactivate the fetcher and queue // re-registration, which will re-active breakpoint fetching. @@ -483,13 +509,14 @@ export class Debuglet extends EventEmitter { return; default: - that.logger_.info('\t' + response.statusCode + ' completed.'); + // TODO: Address the case where `response` is `undefined`. + that.logger_.info('\t' + (response as http.ServerResponse).statusCode + ' completed.'); if (body.wait_expired) { that.logger_.info('\tLong poll completed.'); that.scheduleBreakpointFetch_(0 /*immediately*/); return; } - const bps = (body.breakpoints || []).filter(function(bp) { + const bps = (body.breakpoints || []).filter(function(bp: Breakpoint) { const action = bp.action || 'CAPTURE'; if (action !== 'CAPTURE' && action !== 'LOG') { that.logger_.warn( @@ -531,8 +558,9 @@ export class Debuglet extends EventEmitter { breakpoints.forEach(function(breakpoint: Breakpoint) { - if (!that.completedBreakpointMap_[breakpoint.id] && - !that.activeBreakpointMap_[breakpoint.id]) { + // TODO: Address the case when `breakpoint.id` is `undefined`. + if (!that.completedBreakpointMap_[breakpoint.id as string] && + !that.activeBreakpointMap_[breakpoint.id as string]) { // New breakpoint that.addBreakpoint_(breakpoint, function(err) { if (err) { @@ -551,8 +579,9 @@ export class Debuglet extends EventEmitter { // TODO: FIXME: breakpoint is a boolean here that doesn't have an id // field. It is possible that breakpoint.id is always // undefined! - delete this.completedBreakpointMap_[(breakpoint as any).id]; - }, this); + // TODO: Make sure the use of `that` here is correct. + delete that.completedBreakpointMap_[(breakpoint as any).id]; + }); // Remove active breakpoints that the server no longer care about. Debuglet.mapSubtract(this.activeBreakpointMap_, updatedBreakpointMap) @@ -567,9 +596,10 @@ export class Debuglet extends EventEmitter { */ convertBreakpointListToMap_(breakpointList: Breakpoint[]): {[key: string]: Breakpoint} { - const map = {}; + const map: { [id: string]: Breakpoint } = {}; breakpointList.forEach(function(breakpoint) { - map[breakpoint.id] = breakpoint; + // TODO: Address the case when `breakpoint.id` is `undefined`. + map[breakpoint.id as string] = breakpoint; }); return map; } @@ -580,7 +610,8 @@ export class Debuglet extends EventEmitter { */ removeBreakpoint_(breakpoint: Breakpoint): void { this.logger_.info('\tdeleted breakpoint', breakpoint.id); - delete this.activeBreakpointMap_[breakpoint.id]; + // TODO: Address the case when `breakpoint.id` is `undefined`. + delete this.activeBreakpointMap_[breakpoint.id as string]; if (this.v8debug_) { this.v8debug_.clear(breakpoint); } @@ -616,26 +647,31 @@ export class Debuglet extends EventEmitter { return; } - that.v8debug_.set(breakpoint, function(err1) { + // TODO: Address the case when `that.v8debug_` is `null`. + (that.v8debug_ as V8DebugApi).set(breakpoint, function(err1) { if (err1) { cb(err1); return; } that.logger_.info('\tsuccessfully added breakpoint ' + breakpoint.id); - that.activeBreakpointMap_[breakpoint.id] = breakpoint; + // TODO: Address the case when `breakpoint.id` is `undefined`. + that.activeBreakpointMap_[breakpoint.id as string] = breakpoint; if (breakpoint.action === 'LOG') { - that.v8debug_.log( + // TODO: Address the case when `that.v8debug_` is `null`. + (that.v8debug_ as V8DebugApi).log( breakpoint, function(fmt: string, exprs: string[]) { console.log('LOGPOINT:', Debuglet.format(fmt, exprs)); }, function() { - return that.completedBreakpointMap_[breakpoint.id]; + // TODO: Address the case when `breakpoint.id` is `undefined`. + return that.completedBreakpointMap_[breakpoint.id as string]; }); } else { - that.v8debug_.wait(breakpoint, function(err2) { + // TODO: Address the case when `that.v8debug_` is `null`. + (that.v8debug_ as V8DebugApi).wait(breakpoint, function(err2) { if (err2) { that.logger_.error(err2); cb(err2); @@ -660,11 +696,13 @@ export class Debuglet extends EventEmitter { that.logger_.info('\tupdating breakpoint data on server', breakpoint.id); that.debugletApi_.updateBreakpoint( - that.debuggee_, breakpoint, function(err /*, body*/) { + // TODO: Address the case when `that.debuggee_` is `null`. + (that.debuggee_ as Debuggee), breakpoint, function(err /*, body*/) { if (err) { that.logger_.error('Unable to complete breakpoint on server', err); } else { - that.completedBreakpointMap_[breakpoint.id] = true; + // TODO: Address the case when `breakpoint.id` is `undefined`. + that.completedBreakpointMap_[breakpoint.id as string] = true; that.removeBreakpoint_(breakpoint); } }); @@ -678,8 +716,9 @@ export class Debuglet extends EventEmitter { rejectBreakpoint_(breakpoint: Breakpoint): void { const that = this; + // TODO: Address the case when `that.debuggee_` is `null`. that.debugletApi_.updateBreakpoint( - that.debuggee_, breakpoint, function(err /*, body*/) { + (that.debuggee_ as Debuggee), breakpoint, function(err /*, body*/) { if (err) { that.logger_.error('Unable to complete breakpoint on server', err); } diff --git a/src.ts/agent/scanner.ts b/src.ts/agent/scanner.ts index fc8f9a23..cce9eed4 100644 --- a/src.ts/agent/scanner.ts +++ b/src.ts/agent/scanner.ts @@ -15,20 +15,34 @@ */ import * as crypto from 'crypto'; -import * as findit from 'findit2'; + +import * as events from 'events'; +// TODO: Make this more precise. +const findit: (dir: string) => events.EventEmitter = require('findit2'); + import * as fs from 'fs'; import * as _ from 'lodash'; import * as path from 'path'; -import * as split from 'split'; + +// TODO: Make this more precise. +const split: () => any = require('split'); export interface FileStats { - hash: string; + // TODO: Verify that this member should actually be optional. + hash?: string; lines: number; } -export interface ScanStats { [filename: string]: FileStats; } +// TODO: Update the code so that `undefined is not a possible property value +export interface ScanStats { [filename: string]: FileStats|undefined; } + +export interface ScanResults { + all(): ScanStats; + selectStats(regex: RegExp): ScanStats; + selectFiles(regex: RegExp, baseDir: string): string[]; +} -class ScanResults { +class ScanResultsImpl implements ScanResults { private stats_: ScanStats; /** @@ -62,8 +76,7 @@ class ScanResults { * to determine if the scan results for that filename * should be included in the returned results. */ - selectStats(regex: RegExp): FileStats[] { - // TODO: Typescript: Determine why {} is needed here + selectStats(regex: RegExp): ScanStats | {} { return _.pickBy(this.stats_, function(_, key) { return regex.test(key); }); @@ -97,14 +110,15 @@ class ScanResults { export function scan( shouldHash: boolean, baseDir: string, regex: RegExp, - callback: (err?: Error, results?: ScanResults, hash?: string) => + callback: (err: Error|null, results?: ScanResults, hash?: string) => void): void { - findFiles(baseDir, regex, function(err, fileList) { + findFiles(baseDir, regex, function(err: Error|null, fileList?: string[]) { if (err) { callback(err); return; } - computeStats(fileList, shouldHash, callback); + // TODO: Handle the case where `fileList` is undefined. + computeStats(fileList as string[], shouldHash, callback); }); } @@ -121,19 +135,20 @@ export function scan( // call signature function computeStats( fileList: string[], shouldHash: boolean, - callback: (err?: Error, results?: ScanResults, hash?: string) => + callback: (err: Error|null, results?: ScanResults, hash?: string) => void): void { let pending = fileList.length; // return a valid, if fake, result when there are no js files to hash. if (pending === 0) { - callback(null, new ScanResults({}), 'EMPTY-no-js-files'); + callback(null, new ScanResultsImpl({}), 'EMPTY-no-js-files'); return; } - const hashes: string[] = []; - const statistics = {}; + // TODO: Address the case where the array contains `undefined`. + const hashes: Array = []; + const statistics: ScanStats = {}; fileList.forEach(function(filename) { - stats(filename, shouldHash, function(err, fileStats) { + stats(filename, shouldHash, function(err: Error, fileStats: FileStats|undefined) { if (err) { callback(err); return; @@ -141,7 +156,8 @@ function computeStats( pending--; if (shouldHash) { - hashes.push(fileStats.hash); + // TODO: Address the case when `fileStats` is `undefined` + hashes.push((fileStats as FileStats).hash); } statistics[filename] = fileStats; @@ -154,7 +170,7 @@ function computeStats( const sha1 = crypto.createHash('sha1').update(buffer).digest('hex'); hash = 'SHA1-' + sha1; } - callback(null, new ScanResults(statistics), hash); + callback(null, new ScanResultsImpl(statistics), hash); } }); }); @@ -171,7 +187,7 @@ function computeStats( */ function findFiles( baseDir: string, regex: RegExp, - callback: (err?: Error, fileList?: string[]) => void): void { + callback: (err: Error|null, fileList?: string[]) => void): void { let errored = false; if (!baseDir) { @@ -182,20 +198,20 @@ function findFiles( const find = findit(baseDir); const fileList: string[] = []; - find.on('error', function(err) { + find.on('error', function(err: Error) { errored = true; callback(err); return; }); - find.on('directory', function(dir, _, stop) { + find.on('directory', function(dir: string, _: fs.Stats, stop: () => void) { const base = path.basename(dir); if (base === '.git' || base === 'node_modules') { stop(); // do not descend } }); - find.on('file', function(file) { + find.on('file', function(file: string) { if (regex.test(file)) { fileList.push(file); } @@ -220,8 +236,8 @@ function findFiles( */ function stats( filename: string, shouldHash: boolean, - cb: (err, stats?: FileStats) => void): void { - let shasum; + cb: (err: Error|null, stats?: FileStats) => void): void { + let shasum: crypto.Hash; if (shouldHash) { shasum = crypto.createHash('sha1'); } @@ -229,17 +245,18 @@ function stats( const s = (fs as any).ReadStream(filename); let lines = 0; const byLine = s.pipe(split()); - byLine.on('error', function(e) { + byLine.on('error', function(e: Error) { cb(e); }); - byLine.on('data', function(d) { + byLine.on('data', function(d: string) { if (shouldHash) { shasum.update(d); } lines++; }); byLine.on('end', function() { - let d; + // TODO: Address the case where `d` is `undefined`. + let d: string | undefined; if (shouldHash) { d = shasum.digest('hex'); } diff --git a/src.ts/agent/sourcemapper.ts b/src.ts/agent/sourcemapper.ts index d70a4244..9158c9e9 100644 --- a/src.ts/agent/sourcemapper.ts +++ b/src.ts/agent/sourcemapper.ts @@ -22,10 +22,10 @@ import * as sourceMap from 'source-map'; /** @define {string} */ const MAP_EXT = '.map'; -interface MapInfoInput { +export interface MapInfoInput { outputFile: string; mapFile: string; - mapConsumer: sourceMap.SourceMapConsumer; + mapConsumer: sourceMap.RawSourceMap; } export interface MapInfoOutput { @@ -36,11 +36,11 @@ export interface MapInfoOutput { export function create( sourcemapPaths: string[], - callback: (err?: Error, mapper?: SourceMapper) => void): void { + callback: (err: Error|null, mapper?: SourceMapper) => void): void { const mapper = new SourceMapper(); const callList = - Array.prototype.slice.call(sourcemapPaths).map(function(path) { - return function(cb) { + Array.prototype.slice.call(sourcemapPaths).map(function(path: string) { + return function(cb: (err: Error|null) => void) { processSourcemap(mapper.infoMap_, path, cb); }; }); @@ -64,7 +64,7 @@ export function create( */ function processSourcemap( infoMap: Map, mapPath: string, - callback: (err?: Error) => void): void { + callback: (err: Error|null) => void): void { // this handles the case when the path is undefined, null, or // the empty string if (!mapPath || !_.endsWith(mapPath, MAP_EXT)) { @@ -75,15 +75,18 @@ function processSourcemap( } mapPath = path.normalize(mapPath); - fs.readFile(mapPath, 'utf8', function(err, data) { + fs.readFile(mapPath, 'utf8', function(err: Error, data: string) { if (err) { return callback( new Error('Could not read sourcemap file ' + mapPath + ': ' + err)); } - let consumer; + let consumer: sourceMap.RawSourceMap; try { - consumer = new sourceMap.SourceMapConsumer(data); + // TODO: Determine how to reconsile the type conflict where `consumer` + // is constructed as a SourceMapConsumer but is used as a + // RawSourceMap. + consumer = new sourceMap.SourceMapConsumer(data) as any as sourceMap.RawSourceMap; } catch (e) { return callback(new Error( 'An error occurred while reading the ' + @@ -102,12 +105,12 @@ function processSourcemap( const outputPath = path.normalize(path.join(parentDir, outputBase)); const sources = Array.prototype.slice.call(consumer.sources) - .filter(function(value) { + .filter(function(value: string) { // filter out any empty string, null, or undefined // sources return !!value; }) - .map(function(relPath) { + .map(function(relPath: string) { // resolve the paths relative to the map file so that // they are relative to the process's current working // directory @@ -119,7 +122,7 @@ function processSourcemap( new Error('No sources listed in the sourcemap file ' + mapPath)); } - sources.forEach(function(src) { + sources.forEach(function(src: string) { infoMap.set( path.normalize(src), {outputFile: outputPath, mapFile: mapPath, mapConsumer: consumer}); @@ -187,7 +190,8 @@ export class SourceMapper { return null; } - const entry = this.infoMap_.get(inputPath); + // TODO: `entry` could be `undefined` here. Address this case. + const entry = this.infoMap_.get(inputPath) as any as MapInfoInput; const sourcePos = { source: path.relative(path.dirname(entry.mapFile), inputPath), line: lineNumber + 1, // the SourceMapConsumer expects the line number @@ -195,7 +199,8 @@ export class SourceMapper { column: colNumber // to be zero-based }; - const consumer = entry.mapConsumer; + // TODO: Determine how to remove the explicit cast here. + const consumer: sourceMap.SourceMapConsumer = entry.mapConsumer as any as sourceMap.SourceMapConsumer; const allPos = consumer.allGeneratedPositionsFor(sourcePos); /* * Based on testing, it appears that the following code is needed to @@ -204,10 +209,10 @@ export class SourceMapper { * In particular, the generatedPositionFor() alone doesn't appear to * give the correct mapping information. */ - const mappedPos = allPos && allPos.length > 0 ? + const mappedPos: sourceMap.Position = allPos && allPos.length > 0 ? Array.prototype.reduce.call( allPos, - function(accumulator, value /*, index, arr*/) { + function(accumulator: sourceMap.Position, value: sourceMap.Position /*, index, arr*/) { return value.line < accumulator.line ? value : accumulator; }) : consumer.generatedPositionFor(sourcePos); @@ -217,9 +222,12 @@ export class SourceMapper { line: mappedPos.line - 1, // convert the one-based line numbers returned // by the SourceMapConsumer to the expected // zero-based output. - column: mappedPos.col // SourceMapConsumer uses zero-based column - // numbers which is the same as the expected - // output + // TODO: The `sourceMap.Position` type definition has a `column` + // attribute and not a `col` attribute. Determine if the type + // definition or this code is correct. + column: (mappedPos as any).col // SourceMapConsumer uses zero-based column + // numbers which is the same as the + // expected output }; } } diff --git a/src.ts/agent/state.ts b/src.ts/agent/state.ts index 66113a28..f297d99f 100644 --- a/src.ts/agent/state.ts +++ b/src.ts/agent/state.ts @@ -27,6 +27,7 @@ import {StatusMessage} from '../status-message'; import * as v8Types from '../types/v8-types'; import * as apiTypes from '../types/api-types'; +import { DebugAgentConfig } from './config'; // TODO: Determine if `ScopeType` should be named `scopeType`. // tslint:disable-next-line:variable-name @@ -46,7 +47,7 @@ const ARG_LOCAL_LIMIT_MESSAGE_INDEX = 3; * evaluatedExpressions fields */ export function capture( - execState: v8Types.ExecutionState, expressions: string[], config, + execState: v8Types.ExecutionState, expressions: string[], config: DebugAgentConfig, v8: v8Types.Debug): apiTypes.Breakpoint { return (new StateResolver(execState, expressions, config, v8)).capture_(); } @@ -58,8 +59,8 @@ export function capture( * * @return an object with error and mirror fields. */ -export function evaluate(expression, frame: v8Types.FrameMirror): - {error?: string, mirror?: v8Types.ValueMirror} { +export function evaluate(expression: string, frame: v8Types.FrameMirror): + {error: string|null, mirror?: v8Types.ValueMirror} { // First validate the expression to make sure it doesn't mutate state const acorn = require('acorn'); try { @@ -84,13 +85,13 @@ export function evaluate(expression, frame: v8Types.FrameMirror): class StateResolver { private state_: v8Types.ExecutionState; private expressions_: string[]; - private config_; + private config_: DebugAgentConfig; private ctx_: v8Types.Debug; private evaluatedExpressions_: apiTypes.Variable[]; private totalSize_: number; private messageTable_: apiTypes.Variable[]; private resolvedVariableTable_: apiTypes.Variable[]; - private rawVariableTable_: Array; + private rawVariableTable_: Array; /** * @param {!Object} execState @@ -99,7 +100,7 @@ class StateResolver { * @constructor */ constructor( - execState: v8Types.ExecutionState, expressions: string[], config, + execState: v8Types.ExecutionState, expressions: string[], config: DebugAgentConfig, v8: v8Types.Debug) { this.state_ = execState; this.expressions_ = expressions; @@ -165,7 +166,8 @@ class StateResolver { }; } else { // TODO: Determine how to not downcast this to v8Types.ValueMirror - evaluated = that.resolveVariable_(expression, result.mirror, true); + // TODO: Handle the case where `result.mirror` is `undefined`. + evaluated = that.resolveVariable_(expression, result.mirror as v8Types.ValueMirror, true); const varTableIdx = evaluated.varTableIndex; if (typeof varTableIdx !== 'undefined') { evalIndexSet.add(varTableIdx); @@ -299,12 +301,14 @@ class StateResolver { stripCurrentWorkingDirectory_(path: string): string { // Strip 1 extra character to remove the slash. - return path.substr(this.config_.workingDirectory.length + 1); + // TODO: Handle the case where `this.config_.workingDirectory` is `null`. + return path.substr((this.config_.workingDirectory as string).length + 1); } isPathInCurrentWorkingDirectory_(path: string): boolean { // return true; - return path.indexOf(this.config_.workingDirectory) === 0; + // TODO: Handle the case where `this.config_.workingDirectory` is `null`. + return path.indexOf(this.config_.workingDirectory as string) === 0; } isPathInNodeModulesDirectory_(path: string): boolean { @@ -398,7 +402,7 @@ class StateResolver { args = args; const self = this; - const usedNames = {}; + const usedNames: { [name: string]: boolean } = {}; const makeMirror = this.ctx_.MakeMirror; const allScopes = frame.allScopes(); const count = allScopes.length; @@ -413,7 +417,7 @@ class StateResolver { // We find the top-level (module global) variable pollute the local // variables we omit them by default, unless the breakpoint itself is // top-level. The last two scopes are always omitted. - let scopes; + let scopes: v8Types.ScopeMirror[]; if (allScopes[count - 3].scopeType() === ScopeType.Closure) { scopes = allScopes.slice(0, -3); } else { @@ -421,9 +425,10 @@ class StateResolver { scopes = allScopes.slice(0, -2); } - return flatten(scopes.map(function(scope) { + return flatten(scopes.map(function(scope: v8Types.ScopeMirror) { return transform( - scope.details().object(), function(locals, value, name) { + // TODO: Update this so that `locals` is not of type `any[]`. + scope.details().object(), function(locals: any[], value, name: string) { const trg = makeMirror(value); if (!usedNames[name]) { // It's a valid variable that belongs in the locals list diff --git a/src.ts/agent/v8debugapi.ts b/src.ts/agent/v8debugapi.ts index 828e3c77..ad448a2b 100644 --- a/src.ts/agent/v8debugapi.ts +++ b/src.ts/agent/v8debugapi.ts @@ -27,7 +27,7 @@ import {Logger} from '../types/common-types'; import * as v8Types from '../types/v8-types'; import {DebugAgentConfig} from './config'; -import {FileStats} from './scanner'; +import {FileStats,ScanStats} from './scanner'; import {MapInfoOutput, SourceMapper} from './sourcemapper'; import * as state from './state'; @@ -53,7 +53,7 @@ const messages = { const MODULE_WRAP_PREFIX_LENGTH = require('module').wrap('☃').indexOf('☃'); export interface V8DebugApi { - set: (breakpoint: apiTypes.Breakpoint, cb: (err?: Error) => void) => void; + set: (breakpoint: apiTypes.Breakpoint, cb: (err: Error|null) => void) => void; clear: (breakpoint: apiTypes.Breakpoint) => boolean; wait: (breakpoint: apiTypes.Breakpoint, @@ -69,6 +69,10 @@ export interface V8DebugApi { interface BreakPointData { v8Breakpoint: v8Types.BreakPoint; + // TODO: The code in this method assumes this method exists. Verify that + // is correct. + // TODO: Update this so that `null|` is not needed here. + compile: null|((text: string) => string); } /** @@ -85,20 +89,21 @@ const formatInterval = function(msg: string, interval: number[]): string { let singleton: V8DebugApi; export function create( - logger_: Logger, config_: DebugAgentConfig, jsFiles_: FileStats[], - sourcemapper_: SourceMapper): V8DebugApi { + logger_: Logger, config_: DebugAgentConfig, jsFiles_: ScanStats, + sourcemapper_: SourceMapper): V8DebugApi|null { if (singleton && !config_.forceNewAgent_) { return singleton; } - let v8: v8Types.Debug = null; - let logger: Logger = null; - let config: DebugAgentConfig = null; - let fileStats: FileStats[] = null; - let breakpoints: {[id: number]: BreakPointData} = {}; - let sourcemapper: SourceMapper = null; + let v8: v8Types.Debug|null = null; + let logger: Logger|null = null; + let config: DebugAgentConfig|null = null; + let fileStats: ScanStats|null = null; + let breakpoints: {[id: string]: BreakPointData} = {}; + let sourcemapper: SourceMapper|null = null; // Entries map breakpoint id to { enabled: , listener: } - const listeners = {}; + // TODO: Determine if the listener type is correct + const listeners: { [id: string]: { enabled: boolean; listener: (...args: any[]) => any; } } = {}; let numBreakpoints = 0; // Before V8 4.5, having a debug listener active disables optimization. To // deal with this we only activate the listener when there is a breakpoint @@ -125,7 +130,8 @@ export function create( if (usePermanentListener) { logger.info('activating v8 breakpoint listener (permanent)'); - v8.setListener(handleDebugEvents); + // TODO: Address the case where `v8` is `null`. + (v8 as v8Types.Debug).setListener(handleDebugEvents); } /* -- Public Interface -- */ @@ -137,7 +143,7 @@ export function create( * argument */ set: function( - breakpoint: apiTypes.Breakpoint, cb: (err?: Error) => void): void { + breakpoint: apiTypes.Breakpoint, cb: (err: Error|null) => void): void { if (!v8 || !breakpoint || typeof breakpoint.id === 'undefined' || // 0 is a valid id !breakpoint.location || !breakpoint.location.path || @@ -148,7 +154,8 @@ export function create( } const baseScriptPath = path.normalize(breakpoint.location.path); - if (!sourcemapper.hasMappingInfo(baseScriptPath)) { + // TODO: Address the case where `sourcemapper` is `null`. + if (!(sourcemapper as SourceMapper).hasMappingInfo(baseScriptPath)) { if (!_.endsWith(baseScriptPath, '.js')) { return setErrorStatusAndCallback( cb, breakpoint, StatusMessage.BREAKPOINT_SOURCE_LOCATION, @@ -159,14 +166,16 @@ export function create( } else { const line = breakpoint.location.line; const column = 0; - const mapInfo = sourcemapper.mappingInfo(baseScriptPath, line, column); + // TODO: Address the case where `sourcemapper` is `null`. + const mapInfo = (sourcemapper as SourceMapper).mappingInfo(baseScriptPath, line, column); const compile = getBreakpointCompiler(breakpoint); if (breakpoint.condition && compile) { try { breakpoint.condition = compile(breakpoint.condition); } catch (e) { - logger.info( + // TODO: Address the case where `Logger` is `null`. + (logger as Logger).info( 'Unable to compile condition >> ' + breakpoint.condition + ' <<'); return setErrorStatusAndCallback( @@ -189,14 +198,17 @@ export function create( } const v8bp = breakpointData.v8Breakpoint; - v8.clearBreakPoint(v8bp.number()); + // TODO: Address the case where `v8` is `null`. + (v8 as v8Types.Debug).clearBreakPoint(v8bp.number()); delete breakpoints[breakpoint.id]; delete listeners[v8bp.number()]; numBreakpoints--; if (numBreakpoints === 0 && !usePermanentListener) { // removed last breakpoint - logger.info('deactivating v8 breakpoint listener'); - v8.setListener(null); + // TODO: Address the case where `logger` is `null`. + (logger as Logger).info('deactivating v8 breakpoint listener'); + // TODO: Address the case where `v8` is `null`. + (v8 as v8Types.Debug).setListener(null); } return true; }, @@ -208,8 +220,9 @@ export function create( wait: function( breakpoint: apiTypes.Breakpoint, callback: (err?: Error) => void): void { - const num = breakpoints[breakpoint.id].v8Breakpoint.number(); - const listener = onBreakpointHit.bind(null, breakpoint, function(err) { + // TODO: Address the case whree `breakpoint.id` is `null`. + const num = breakpoints[breakpoint.id as string].v8Breakpoint.number(); + const listener = onBreakpointHit.bind(null, breakpoint, function(err: Error) { listeners[num].enabled = false; // This method is called from the debug event listener, which // swallows all exception. We defer the callback to make sure the @@ -230,17 +243,20 @@ export function create( breakpoint: apiTypes.Breakpoint, print: (format: string, exps: string[]) => void, shouldStop: () => boolean): void { - const num = breakpoints[breakpoint.id].v8Breakpoint.number(); + // TODO: Address the case whree `breakpoint.id` is `null`. + const num = breakpoints[breakpoint.id as string].v8Breakpoint.number(); let logsThisSecond = 0; let timesliceEnd = Date.now() + 1000; - const listener = onBreakpointHit.bind(null, breakpoint, function(_) { + // TODO: Determine why the Error argument is not used. + const listener = onBreakpointHit.bind(null, breakpoint, function(_: Error) { const currTime = Date.now(); if (currTime > timesliceEnd) { logsThisSecond = 0; timesliceEnd = currTime + 1000; } print( - breakpoint.logMessageFormat, + // TODO: Address the case where `breakpoint.logMessageFormat` is `null`. + breakpoint.logMessageFormat as string, // TODO: Determine how to remove the `as` cast below breakpoint.evaluatedExpressions.map( JSON.stringify as (ob: any) => string)); @@ -248,7 +264,8 @@ export function create( if (shouldStop()) { listeners[num].enabled = false; } else { - if (logsThisSecond >= config.log.maxLogsPerSecond) { + // TODO: Address the case where `DebugAgentConfig` is `null`. + if (logsThisSecond >= (config as DebugAgentConfig).log.maxLogsPerSecond) { listeners[num].enabled = false; setTimeout(function() { // listeners[num] may have been deleted by `clear` during the @@ -257,7 +274,8 @@ export function create( if (!shouldStop() && listeners[num]) { listeners[num].enabled = true; } - }, config.log.logDelaySeconds * 1000); + // TODO: Address the case where `config` is `null`. + }, (config as DebugAgentConfig).log.logDelaySeconds * 1000); } } }); @@ -288,8 +306,8 @@ export function create( */ // TODO: Fix the documented types to match the function's input types function setInternal( - breakpoint: apiTypes.Breakpoint, mapInfo: MapInfoOutput, - compile: (src: string) => string, cb: (err?: Error) => void): void { + breakpoint: apiTypes.Breakpoint, mapInfo: MapInfoOutput|null, + compile: ((src: string) => string)|null, cb: (err: Error|null) => void): void { // Parse and validate conditions and watch expressions for correctness and // immutability let ast = null; @@ -325,9 +343,12 @@ export function create( // debuglet, we are going to assume that repository root === the starting // working directory. let matchingScript; + // TODO: Address the case where `breakpoint.location` is `null`. + // TODO: Address the case where `config` is `null`. + // TODO: Address the case where `fileStats` is `null`. const scripts = findScripts( - mapInfo ? mapInfo.file : path.normalize(breakpoint.location.path), - config, fileStats); + mapInfo ? mapInfo.file : path.normalize((breakpoint.location as apiTypes.SourceLocation).path), + config as DebugAgentConfig, fileStats as ScanStats); if (scripts.length === 0) { return setErrorStatusAndCallback( cb, breakpoint, StatusMessage.BREAKPOINT_SOURCE_LOCATION, @@ -341,21 +362,25 @@ export function create( messages.SOURCE_FILE_AMBIGUOUS); } - if (breakpoint.location.line >= fileStats[matchingScript].lines) { + // TODO: Address the case where `breakpoint.location` is `null`. + // TODO: Address the case where `fileStats` is `null`. + // TODO: Address the case where `fileStats[matchingScript]` is `null`. + if ((breakpoint.location as apiTypes.SourceLocation).line >= ((fileStats as ScanStats)[matchingScript] as FileStats).lines) { return setErrorStatusAndCallback( cb, breakpoint, StatusMessage.BREAKPOINT_SOURCE_LOCATION, messages.INVALID_LINE_NUMBER + matchingScript + ':' + - breakpoint.location.line + '. Loaded script contained ' + - fileStats[matchingScript].lines + ' lines. Please ensure' + + (breakpoint.location as apiTypes.SourceLocation).line + '. Loaded script contained ' + + ((fileStats as ScanStats)[matchingScript] as FileStats).lines + ' lines. Please ensure' + ' that the snapshot was set in the same code version as the' + ' deployed source.'); } // The breakpoint protobuf message presently doesn't have a column property // but it may have one in the future. + // TODO: Address the case where `breakpoint.location` is `null`. let column = mapInfo && mapInfo.column ? mapInfo.column : - (breakpoint.location.column || 1); - const line = mapInfo ? mapInfo.line : breakpoint.location.line; + ((breakpoint.location as apiTypes.SourceLocation).column || 1); + const line = mapInfo ? mapInfo.line : (breakpoint.location as apiTypes.SourceLocation).line; // We need to special case breakpoints on the first line. Since Node.js // wraps modules with a function expression, we adjust @@ -373,12 +398,16 @@ export function create( if (numBreakpoints === 0 && !usePermanentListener) { // added first breakpoint - logger.info('activating v8 breakpoint listener'); - v8.setListener(handleDebugEvents); + // TODO: Address the case where `logger` is `null`. + (logger as Logger).info('activating v8 breakpoint listener'); + // TODO: Address the case where `v8` is `null`. + (v8 as v8Types.Debug).setListener(handleDebugEvents); } - breakpoints[breakpoint.id] = - new BreakpointData(breakpoint, v8bp, ast, compile); + // TODO: Address the case whree `breakpoint.id` is `null`. + breakpoints[breakpoint.id as string] = + // TODO: Address the case where `ast` is `null`. + new BreakpointData(breakpoint, v8bp, ast as estree.Program, compile); numBreakpoints++; setImmediate(function() { @@ -394,7 +423,8 @@ export function create( */ function getBreakpointCompiler(breakpoint: apiTypes.Breakpoint): ((uncompiled: string) => string)|null { - switch (path.normalize(breakpoint.location.path).split('.').pop()) { + // TODO: Address the case where `breakpoint.location` is `null`. + switch (path.normalize((breakpoint.location as apiTypes.SourceLocation).path).split('.').pop()) { case 'coffee': return function(uncompiled) { const comp = require('coffee-script'); @@ -426,8 +456,9 @@ export function create( function setByRegExp( scriptPath: string, line: number, column: number): v8Types.BreakPoint { const regexp = pathToRegExp(scriptPath); - const num = v8.setScriptBreakPointByRegExp(regexp, line - 1, column - 1); - const v8bp = v8.findBreakPoint(num); + // TODO: Address the case where `v8` is a `null`. + const num = (v8 as v8Types.Debug).setScriptBreakPointByRegExp(regexp, line - 1, column - 1); + const v8bp = (v8 as v8Types.Debug).findBreakPoint(num); return v8bp; } @@ -454,9 +485,10 @@ export function create( // } function onBreakpointHit( - breakpoint: apiTypes.Breakpoint, callback: (err?: Error) => void, + breakpoint: apiTypes.Breakpoint, callback: (err: Error|null) => void, execState: v8Types.ExecutionState): void { - const v8bp = breakpoints[breakpoint.id].v8Breakpoint; + // TODO: Address the situation where `breakpoint.id` is `null`. + const v8bp = breakpoints[breakpoint.id as string].v8Breakpoint; if (!v8bp.active()) { // Breakpoint exists, but not active. We never disable breakpoints, so @@ -474,7 +506,8 @@ export function create( messages.ERROR_EVALUATING_CONDITION + result.error); } else if (!result.value) { // Check again next time - logger.info('\tthe breakpoint condition wasn\'t met'); + // TODO: Address the point where `logger` is `null`. + (logger as Logger).info('\tthe breakpoint condition wasn\'t met'); return; } @@ -488,7 +521,8 @@ export function create( messages.CAPTURE_BREAKPOINT_DATA + err); } const end = process.hrtime(start); - logger.info(formatInterval('capture time: ', end)); + // TODO: Address the point where `logger` is `null`. + (logger as Logger).info(formatInterval('capture time: ', end)); callback(null); } @@ -502,11 +536,13 @@ export function create( eventData: v8Types.BreakEvent): void { try { switch (evt) { - case v8.DebugEvent.Break: + // TODO: Address the case where `v8` is `null`. + case (v8 as v8Types.Debug).DebugEvent.Break: eventData.breakPointsHit().forEach(function(hit) { const num = hit.script_break_point().number(); if (listeners[num].enabled) { - logger.info('>>>V8 breakpoint hit<<< number: ' + num); + // TODO: Address the case where `logger` is `null`. + (logger as Logger).info('>>>V8 breakpoint hit<<< number: ' + num); listeners[num].listener(execState, eventData); } }); @@ -514,21 +550,26 @@ export function create( default: } } catch (e) { - logger.warn('Internal V8 error on breakpoint event: ' + e); + // TODO: Address the case where `logger` is `null`. + (logger as Logger).warn('Internal V8 error on breakpoint event: ' + e); } } function captureBreakpointData( breakpoint: apiTypes.Breakpoint, execState: v8Types.ExecutionState): void { - const expressionErrors = []; - if (breakpoint.expressions && breakpoints[breakpoint.id].compile) { + const expressionErrors: Array = []; + // TODO: Address the case where `breakpoint.id` is `null`. + if (breakpoint.expressions && breakpoints[breakpoint.id as string].compile) { for (let i = 0; i < breakpoint.expressions.length; i++) { try { + // TODO: Address the case where `breakpoint.id` is `null`. breakpoint.expressions[i] = - breakpoints[breakpoint.id].compile(breakpoint.expressions[i]); + // TODO: Address the case where `compile` is `null`. + (breakpoints[breakpoint.id as string].compile as (text: string)=>string)(breakpoint.expressions[i]); } catch (e) { - logger.info( + // TODO: Address the case where `logger` is `null`. + (logger as Logger).info( 'Unable to compile watch expression >> ' + breakpoint.expressions[i] + ' <<'); expressionErrors.push({ @@ -550,13 +591,17 @@ export function create( const frame = execState.frame(0); const evaluatedExpressions = breakpoint.expressions.map(function(exp) { const result = state.evaluate(exp, frame); - return result.error ? result.error : result.mirror.value(); + // TODO: Address the case where `result.mirror` is `undefined`. + return result.error ? result.error : (result.mirror as v8Types.ValueMirror).value(); }); breakpoint.evaluatedExpressions = evaluatedExpressions; } } else { + // TODO: Address the case where `breakpoint.expression` is `undefined`. + // TODO: Address the case where `config` is `undefined`. + // TODO: Address the case whre `v8` is `undefined`. const captured = - state.capture(execState, breakpoint.expressions, config, v8); + state.capture(execState, breakpoint.expressions as string[], config as DebugAgentConfig, v8 as v8Types.Debug); breakpoint.stackFrames = captured.stackFrames; // TODO: This suggests the Status type and Variable type are the same. // Determine if that is the case. @@ -582,21 +627,22 @@ export function create( if (result.error) { return {error: result.error}; } - return {value: !!(result.mirror.value())}; // intentional !! + // TODO: Address the case where `result.mirror` is `null`. + return {value: !!((result.mirror as v8Types.ValueMirror).value())}; // intentional !! } class BreakpointData { apiBreakpoint: apiTypes.Breakpoint; v8Breakpoint: v8Types.BreakPoint; parsedCondition: estree.Node; - compile: (src: string) => string; + compile: null|((src: string) => string); /** * @constructor */ constructor( apiBreakpoint: apiTypes.Breakpoint, v8Breakpoint: v8Types.BreakPoint, - parsedCondition: estree.Node, compile: (src: string) => string) { + parsedCondition: estree.Node, compile: null|((src: string) => string)) { this.apiBreakpoint = apiBreakpoint; this.v8Breakpoint = v8Breakpoint; this.parsedCondition = parsedCondition; @@ -605,7 +651,7 @@ export function create( } function setErrorStatusAndCallback( - fn: (err?: Error) => void, breakpoint: apiTypes.Breakpoint, + fn: (err: Error|null) => void, breakpoint: apiTypes.Breakpoint, refersTo: apiTypes.Reference, message: string): void { const error = new Error(message); return setImmediate(function() { @@ -640,11 +686,12 @@ function pathToRegExp(scriptPath: string): RegExp { // Exposed for unit testing. export function findScripts( scriptPath: string, config: DebugAgentConfig, - fileStats: FileStats[]): string[] { + fileStats: ScanStats): string[] { // Use repository relative mapping if present. if (config.appPathRelativeToRepository) { const candidate = scriptPath.replace( - config.appPathRelativeToRepository, config.workingDirectory); + // TODO: Address the case where `config.workingDirectory` is `null`. + config.appPathRelativeToRepository, config.workingDirectory as string); // There should be no ambiguity resolution if project root is provided. return fileStats[candidate] ? [candidate] : []; } diff --git a/src.ts/controller.ts b/src.ts/controller.ts index 407e0986..08673b6f 100644 --- a/src.ts/controller.ts +++ b/src.ts/controller.ts @@ -18,7 +18,9 @@ * @module debug/controller */ -import * as _common from '@google-cloud/common'; +import {Common} from './types/common-types'; +export const common: Common = require('@google-cloud/common'); + import * as assert from 'assert'; import * as http from 'http'; import * as qs from 'querystring'; @@ -26,15 +28,12 @@ import * as qs from 'querystring'; import {Debug} from './debug'; import {Debuggee} from './debuggee'; import {Breakpoint} from './types/api-types'; -import {Common} from './types/common-types'; - -const common: Common = _common; /** @const {string} Cloud Debug API endpoint */ const API = 'https://clouddebugger.googleapis.com/v2/controller'; export class Controller extends common.ServiceObject { - private nextWaitToken_?: string; + private nextWaitToken_: string | null; /** * @constructor @@ -52,7 +51,7 @@ export class Controller extends common.ServiceObject { * @param {!function(?Error,Object=)} callback * @private */ - register(debuggee: Debuggee, callback: (err?: Error, result?: { + register(debuggee: Debuggee, callback: (err: Error|null, result?: { debuggee: Debuggee }) => void): void { const options = { @@ -87,7 +86,7 @@ export class Controller extends common.ServiceObject { listBreakpoints( debuggee: Debuggee, callback: - (err?: Error, response?: http.ServerResponse, body?: any) => void): + (err: Error|null, response?: http.ServerResponse, body?: any) => void): void { const that = this; assert(debuggee.id, 'should have a registered debuggee'); @@ -138,7 +137,8 @@ export class Controller extends common.ServiceObject { breakpoint.isFinalState = true; const options = { uri: API + '/debuggees/' + encodeURIComponent(debuggee.id) + - '/breakpoints/' + encodeURIComponent(breakpoint.id), + // TODO: Address the case where `breakpoint.id` is `undefined`. + '/breakpoints/' + encodeURIComponent(breakpoint.id as string), json: true, method: 'PUT', body: {debuggeeId: debuggee.id, breakpoint: breakpoint} diff --git a/src.ts/debug.ts b/src.ts/debug.ts index 2b8de738..c7821a96 100644 --- a/src.ts/debug.ts +++ b/src.ts/debug.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import * as _common from '@google-cloud/common'; - import {AuthOptions, Common} from './types/common-types'; +const common: Common = require('@google-cloud/common'); -const common: Common = _common; import * as util from 'util'; export class Debug { diff --git a/src.ts/debuggee.ts b/src.ts/debuggee.ts index 6adabedb..19a51a81 100644 --- a/src.ts/debuggee.ts +++ b/src.ts/debuggee.ts @@ -22,7 +22,7 @@ import {StatusMessage} from './status-message'; // docs // In particular, the comments below state some of the properties are // required but the default properties in the code is {} -interface DebuggeeProperties { +export interface DebuggeeProperties { project?: string; uniquifier?: string; description?: string; @@ -32,7 +32,7 @@ interface DebuggeeProperties { [key: string]: string, }; sourceContexts?: Array<{[key: string]: any}>; - statusMessage?: StatusMessage; + statusMessage: StatusMessage|null; } export class Debuggee { @@ -45,7 +45,7 @@ export class Debuggee { }; private sourceContexts?: Array<{[key: string]: any}>; private statusMessage?: StatusMessage; - id?: string; + id: string; // TODO: This doesn't seem to ever be set but is referenced in the // debuglet.ts file. isDisabled?: boolean; diff --git a/src.ts/index.ts b/src.ts/index.ts index 030c9203..e956f865 100644 --- a/src.ts/index.ts +++ b/src.ts/index.ts @@ -20,7 +20,7 @@ import {Debug} from './debug'; import {AuthOptions} from './types/common-types'; // Singleton. -let debuglet; +let debuglet: Debuglet; /** * Start the Debug agent that will make your application available for debugging diff --git a/src.ts/types/api-types.ts b/src.ts/types/api-types.ts index 6d157dda..2c039d7e 100644 --- a/src.ts/types/api-types.ts +++ b/src.ts/types/api-types.ts @@ -72,11 +72,15 @@ export interface Timestamp { export interface Breakpoint { stackFrames: StackFrame[]; - evaluatedExpressions: Variable[]; - variableTable: Variable[]; + // TODO: Update the code so that `|null` is not needed. + evaluatedExpressions: Array; + // TODO: Update the code so that `|null` is not needed. + variableTable: Array; + // TODO: The `controller.ts` file assumes `id` is not null or undefined. + // Verify it it should be optional. + id?: string; // TODO: The debug code assumes the rest of these members // are optional. Determine if this is correct. - id?: string; action?: Action; location?: SourceLocation; condition?: string; diff --git a/src.ts/types/common-types.ts b/src.ts/types/common-types.ts index 7f543f90..11b49535 100644 --- a/src.ts/types/common-types.ts +++ b/src.ts/types/common-types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import * as http from 'http'; + export interface AuthOptions { credentials?: {client_email: string; private_key: string;}; keyFilename?: string; @@ -34,20 +36,39 @@ export interface ServiceObjectConfig { methods?: any; } -export interface Common { - Service: new(config: ServiceConfig, options: AuthOptions) => any; - ServiceObject: new(config: ServiceObjectConfig) => any; - util: { - // TODO: Make this more precise. - normalizeArguments: (globalContext: any, localConfig: any, options?: any) => - any; - }; +export interface LoggerOptions { + level?: string; + levels?: string[]; + tag: string; } export interface Logger { + new(options?: string | LoggerOptions): Logger; + LEVELS: string[]; // TODO: Determine the correct signatures for these members error: (message: any, ...args: any[]) => void; warn: (message: any, ...args: any[]) => void; info: (message: any, ...args: any[]) => void; debug: (message: any, ...args: any[]) => void; } + +export interface Service { + new(config: ServiceConfig, options: AuthOptions): Service; +} + +export interface ServiceObject { + new(config: ServiceObjectConfig): ServiceObject; + // TODO: Determine if this signature is correct. + request: (reqOpts: { uri: string, json: boolean }, callback: (err: Error, body: any, response: http.ServerResponse) => void) => void; +} + +export interface Common { + Service: Service; + ServiceObject: ServiceObject; + logger: Logger; + util: { + // TODO: Make this more precise. + normalizeArguments: (globalContext: any, localConfig: any, options?: any) => + any; + }; +} diff --git a/src.ts/types/gcp-metadata-types.ts b/src.ts/types/gcp-metadata-types.ts new file mode 100644 index 00000000..7a0b17e4 --- /dev/null +++ b/src.ts/types/gcp-metadata-types.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as http from 'http'; + +export interface GcpMetadata { + // TODO: Determine if the signature of the callback on these methods are + // correct. + instance: (options: string | { property: string}, callback: (err: Error, response: http.ServerResponse, metadataProject: string) => void) => http.ServerResponse; + project: (options: string | { property: string}, callback: (err: Error, response: http.ServerResponse, metadataProject: string) => void) => http.ServerResponse; +} diff --git a/src.ts/types/v8-types.ts b/src.ts/types/v8-types.ts index b7d90120..d2ccd754 100644 --- a/src.ts/types/v8-types.ts +++ b/src.ts/types/v8-types.ts @@ -276,7 +276,12 @@ export interface ExecutionState { } // TODO: Add the rest of the methods in this interface -export interface BreakPoint { script_break_point: () => ScriptBreakPoint; } +export interface BreakPoint { + script_break_point: () => ScriptBreakPoint; + // TODO: The debug code assumes these method exist. Verify they exist. + number: () => number; + active: () => boolean; +} // TODO: Add the rest of the methods in this interface export interface ScriptBreakPoint { number: () => number; } diff --git a/tsconfig.json b/tsconfig.json index 26a59da7..b211d1e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,22 +1,21 @@ { "compilerOptions": { - "allowJs": true, "allowSyntheticDefaultImports": false, "allowUnreachableCode": false, "allowUnusedLabels": false, "alwaysStrict": true, -// "declaration": true, + "declaration": true, "forceConsistentCasingInFileNames": true, "lib": ["es6"], "noFallthroughCasesInSwitch": true, "noEmitOnError": true, -// "noImplicitAny": true, + "noImplicitAny": true, "noImplicitReturns": true, -// "noImplicitThis": true, + "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, "pretty": true, -// "strictNullChecks": true, + "strictNullChecks": true, "module": "commonjs", "target": "es5", "sourceMap": true