microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.75k stars 12.46k forks source link

sourcemaps not working correctly with static properties #35307

Closed christopheranderson closed 8 months ago

christopheranderson commented 4 years ago

TypeScript Version: 3.8.0-dev.20191122 (also reproduced on @latest (3.7.2)

Search Terms: typescript sourcemaps not working with static properties

Code

This issue is specifically when trying to consume the generated sourcemaps in vscode.

From: https://github.com/microsoft/botframework-cli/blob/91881c3be6bf3888f97f86ff7d9b314871bf5d5b/packages/dialog/src/commands/dialog/verify.ts#L14-L29

If the below static properties are placed before other code in my class, then the sourcemap that's generated is off and debugging tools like VS code will have their breakpoints misplaced.

static args = [
        { name: 'glob1', required: true },
        { name: 'glob2', required: false },
        { name: 'glob3', required: false },
        { name: 'glob4', required: false },
        { name: 'glob5', required: false },
        { name: 'glob6', required: false },
        { name: 'glob7', required: false },
        { name: 'glob8', required: false },
        { name: 'glob9', required: false },
    ]

    static flags: flags.Input<any> = {
        help: flags.help({ char: 'h' }),
        verbose: flags.boolean({ description: 'Show verbose output', default: false }),
    }
Additional debugging info (including full source for the file and generated content. (If you attempt to build the project for a local repro, you'll need to look at the contributor guide. We use rush to orchestrate building the projects). launch.json snippet config for verify ```json { "type": "node", "request": "launch", "name": "Dialog Verify Tests", "program": "${workspaceFolder}/packages/dialog/node_modules/mocha/bin/_mocha", "cwd": "${workspaceFolder}/packages/dialog", "args": [ "--timeout", "999999", "--colors", "-g", ".*dialog:verify.*" ], "internalConsoleOptions": "openOnSessionStart", "sourceMaps": true, "outFiles": [ "./package/dialog/lib/**" ] }, ``` generated files: verify ts ```ts /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ import { Command, flags } from '@microsoft/bf-cli-command'; import * as chalk from 'chalk'; import { Definition, DialogTracker, SchemaTracker } from '../../library/dialogTracker'; // import * as process from 'process'; export default class DialogVerify extends Command { static args = [ { name: 'glob1', required: true }, { name: 'glob2', required: false }, { name: 'glob3', required: false }, { name: 'glob4', required: false }, { name: 'glob5', required: false }, { name: 'glob6', required: false }, { name: 'glob7', required: false }, { name: 'glob8', required: false }, { name: 'glob9', required: false }, ] static flags: flags.Input = { help: flags.help({ char: 'h' }), verbose: flags.boolean({ description: 'Show verbose output', default: false }), } private currentFile = '' private files = 0 private errors = 0 private warnings = 0 async run() { const { argv, flags } = this.parse(DialogVerify) await this.execute(argv, flags.verbose) } async execute(dialogFiles: string[], verbose?: boolean): Promise { const schema = new SchemaTracker() const tracker = new DialogTracker(schema) await tracker.addDialogFiles(dialogFiles) if (tracker.dialogs.length === 0) { this.error('No dialogs found!') } else { for (let dialog of tracker.dialogs) { this.files++ this.currentFile = dialog.file if (dialog.errors.length === 0) { if (verbose) { this.consoleLog(`${dialog}`) } } else { for (let error of dialog.errors) { this.consoleError(`${error.message.trim()}`, 'DLG001') } } } for (let defs of tracker.multipleDefinitions()) { let def = (defs as Definition[])[0] this.consoleError(`Multiple definitions for ${def} ${def.usedByString()}`, 'DLG002') for (let def of defs) { this.consoleError(` ${def.pathString()}`, 'DLG002') } } for (let def of tracker.missingDefinitions()) { this.consoleError(`Missing definition for ${def} ${def.usedByString()}`, 'DLG003') } for (let def of tracker.missingTypes) { this.consoleError(`Missing $type for ${def}`, 'DLG004') } for (let def of tracker.unusedIDs()) { this.consoleWarn(`Unused id ${def}`, 'DLG005') } if (verbose) { for (let [type, definitions] of tracker.typeToDef) { this.consoleMsg(`Instances of ${type}`) for (let def of definitions) { this.consoleMsg(` ${def.locatorString()}`) } } } this.log(`${this.files} files processed.`) this.error(`${this.warnings} found.`) if (this.errors > 0) { this.error(`Error: ${this.errors} found.`) } } } consoleMsg(msg: string): void { this.log(chalk.default(msg)) } consoleLog(msg: string): void { this.log(chalk.default.gray(msg)) } consoleWarn(msg: string, code: string): void { this.warnings++ this.warn(`${this.currentFile} - warning ${code || ''}: ${msg}`) } consoleError(msg: string, code: string): void { this.errors++ this.error(`${this.currentFile} - error ${code || ''}: ${msg}`) } } ``` verify js ```js "use strict"; /*! * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ Object.defineProperty(exports, "__esModule", { value: true }); const bf_cli_command_1 = require("@microsoft/bf-cli-command"); const chalk = require("chalk"); const dialogTracker_1 = require("../../library/dialogTracker"); // import * as process from 'process'; class DialogVerify extends bf_cli_command_1.Command { constructor() { super(...arguments); this.currentFile = ''; this.files = 0; this.errors = 0; this.warnings = 0; } async run() { const { argv, flags } = this.parse(DialogVerify); await this.execute(argv, flags.verbose); } async execute(dialogFiles, verbose) { const schema = new dialogTracker_1.SchemaTracker(); const tracker = new dialogTracker_1.DialogTracker(schema); await tracker.addDialogFiles(dialogFiles); if (tracker.dialogs.length === 0) { this.error('No dialogs found!'); } else { for (let dialog of tracker.dialogs) { this.files++; this.currentFile = dialog.file; if (dialog.errors.length === 0) { if (verbose) { this.consoleLog(`${dialog}`); } } else { for (let error of dialog.errors) { this.consoleError(`${error.message.trim()}`, 'DLG001'); } } } for (let defs of tracker.multipleDefinitions()) { let def = defs[0]; this.consoleError(`Multiple definitions for ${def} ${def.usedByString()}`, 'DLG002'); for (let def of defs) { this.consoleError(` ${def.pathString()}`, 'DLG002'); } } for (let def of tracker.missingDefinitions()) { this.consoleError(`Missing definition for ${def} ${def.usedByString()}`, 'DLG003'); } for (let def of tracker.missingTypes) { this.consoleError(`Missing $type for ${def}`, 'DLG004'); } for (let def of tracker.unusedIDs()) { this.consoleWarn(`Unused id ${def}`, 'DLG005'); } if (verbose) { for (let [type, definitions] of tracker.typeToDef) { this.consoleMsg(`Instances of ${type}`); for (let def of definitions) { this.consoleMsg(` ${def.locatorString()}`); } } } this.log(`${this.files} files processed.`); this.error(`${this.warnings} found.`); if (this.errors > 0) { this.error(`Error: ${this.errors} found.`); } } } consoleMsg(msg) { this.log(chalk.default(msg)); } consoleLog(msg) { this.log(chalk.default.gray(msg)); } consoleWarn(msg, code) { this.warnings++; this.warn(`${this.currentFile} - warning ${code || ''}: ${msg}`); } consoleError(msg, code) { this.errors++; this.error(`${this.currentFile} - error ${code || ''}: ${msg}`); } } exports.default = DialogVerify; DialogVerify.args = [ { name: 'glob1', required: true }, { name: 'glob2', required: false }, { name: 'glob3', required: false }, { name: 'glob4', required: false }, { name: 'glob5', required: false }, { name: 'glob6', required: false }, { name: 'glob7', required: false }, { name: 'glob8', required: false }, { name: 'glob9', required: false }, ]; DialogVerify.flags = { help: bf_cli_command_1.flags.help({ char: 'h' }), verbose: bf_cli_command_1.flags.boolean({ description: 'Show verbose output', default: false }), }; //# sourceMappingURL=verify.js.map ``` verify.js.map ```json {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAEH,8DAA2D;AAE3D,MAAqB,KAAM,SAAQ,wBAAO;IAOtC,KAAK,CAAC,GAAG;QACL,IAAI,CAAC,KAAK,EAAE,CAAA;IAChB,CAAC;;AATL,wBAUC;AATU,iBAAW,GAAG,2DAA2D,CAAA;AAEzE,WAAK,GAAqB;IAC7B,IAAI,EAAE,sBAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;CAClC,CAAA"} ```

Expected behavior:

When I set a breakpoint on line 37 & 42, I'd expect it to hit that.

Actual behavior:

It's off by 3 (40 and 45) & it actually thinks its somewhere deep in the execute method.

image

Workaround:

Move the static properties to the bottom of the class and everything works as expected.

Playground Link: N/A

Related Issues: N/A

rbuckton commented 4 years ago

I'm having a hard time reproducing this. Are you able to craft a minimal repro that does not require imports/logic specific to your project? The repro I attempted to create seems to work fine when debugging and breakpoints are hit, and I've verified that the source maps in question properly line up with between the original sources and the generated outputs.

image

image

image

christopheranderson commented 4 years ago

We have a repro in our botframework-cli project, but I'll try to craft a minimal one over the weekend. Thanks for pinging me on this, Ryan.

zuhairtaha commented 8 months ago

I resolved this issue by updating the target to es2022 or any version beyond. Alternatively, you can simply choose esnext, and the generated JavaScript file will use static methods without encountering this problem.

RyanCavanaugh commented 8 months ago

If someone has a concrete repro, please file a new issue with it. Thanks!