From 0e877137d4ea307dab32f225df140c9a4216ebc5 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Fri, 20 Jan 2023 16:08:56 -0500 Subject: [PATCH 1/2] Add hand-written findInFiles function. Fix tests. --- package-lock.json | 256 ++++++++++-------- package.json | 11 +- src/FileUtils.spec.ts | 20 +- src/FileUtils.ts | 143 ++++++---- .../BrightScriptDebugSession.spec.ts | 87 +++--- src/managers/LocationManager.ts | 4 +- src/managers/ProjectManager.ts | 49 +++- 7 files changed, 349 insertions(+), 221 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e2c4653..f455c655 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,13 @@ "dependencies": { "@rokucommunity/logger": "^0.3.0", "brighterscript": "^0.49.0", + "clone-regexp": "2.2.0", "dateformat": "^4.6.3", "eol": "^0.9.1", "eventemitter3": "^4.0.7", - "find-in-files": "^0.5.0", + "fast-glob": "^3.2.11", "fs-extra": "^10.0.0", + "line-column": "^1.0.2", "natural-orderby": "^2.0.3", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", @@ -23,7 +25,7 @@ "semver": "^7.3.5", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", - "source-map": "^0.7.3", + "source-map": "^0.7.4", "telnet-client": "^1.4.9", "vscode-debugadapter": "^1.49.0", "vscode-debugprotocol": "^1.49.0", @@ -34,7 +36,7 @@ "@types/dedent": "^0.7.0", "@types/find-in-files": "^0.5.1", "@types/fs-extra": "^9.0.13", - "@types/glob": "^7.2.0", + "@types/line-column": "^1.0.0", "@types/mocha": "^9.0.0", "@types/node": "^16.11.6", "@types/request": "^2.48.7", @@ -58,7 +60,8 @@ "sinon": "^11.1.2", "source-map-support": "^0.5.20", "ts-node": "^10.4.0", - "typescript": "^4.4.4" + "typescript": "^4.4.4", + "undent": "^0.1.0" } }, "node_modules/@babel/code-frame": { @@ -109,20 +112,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.0", "dev": true, @@ -678,24 +667,16 @@ "@types/node": "*" } }, - "node_modules/@types/glob": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.9", "dev": true, "license": "MIT" }, - "node_modules/@types/minimatch": { - "version": "3.0.5", - "dev": true, - "license": "MIT" + "node_modules/@types/line-column": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/line-column/-/line-column-1.0.0.tgz", + "integrity": "sha512-wbw+IDRw/xY/RGy+BL6f4Eey4jsUgHQrMuA4Qj0CSG3x/7C2Oc57pmRoM2z3M4DkylWRz+G1pfX06sCXQm0J+w==", + "dev": true }, "node_modules/@types/mocha": { "version": "9.0.0", @@ -1563,6 +1544,17 @@ "wrap-ansi": "^6.2.0" } }, + "node_modules/clone-regexp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", + "dependencies": { + "is-regexp": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "license": "MIT", @@ -2152,12 +2144,6 @@ "node": ">=8" } }, - "node_modules/find": { - "version": "0.1.7", - "dependencies": { - "traverse-chain": "~0.1.0" - } - }, "node_modules/find-cache-dir": { "version": "3.3.2", "dev": true, @@ -2185,14 +2171,6 @@ "node": ">=8" } }, - "node_modules/find-in-files": { - "version": "0.5.0", - "license": "MIT", - "dependencies": { - "find": "^0.1.5", - "q": "^1.0.1" - } - }, "node_modules/find-up": { "version": "4.1.0", "dev": true, @@ -2658,6 +2636,14 @@ "node": ">=8" } }, + "node_modules/is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "engines": { + "node": ">=6" + } + }, "node_modules/is-stream": { "version": "2.0.1", "dev": true, @@ -2701,6 +2687,17 @@ "dev": true, "license": "ISC" }, + "node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isstream": { "version": "0.1.2", "license": "MIT" @@ -2858,6 +2855,18 @@ "version": "5.0.1", "license": "ISC" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonc-parser": { "version": "2.3.1", "license": "MIT" @@ -2932,6 +2941,15 @@ "immediate": "~3.0.5" } }, + "node_modules/line-column": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz", + "integrity": "sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI=", + "dependencies": { + "isarray": "^1.0.0", + "isobject": "^2.0.0" + } + }, "node_modules/locate-path": { "version": "5.0.0", "dev": true, @@ -2999,8 +3017,9 @@ } }, "node_modules/luxon": { - "version": "1.28.0", - "license": "MIT", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", "engines": { "node": "*" } @@ -3068,8 +3087,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "license": "ISC", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3311,9 +3331,9 @@ } }, "node_modules/moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", "engines": { "node": "*" } @@ -3699,17 +3719,10 @@ "node": ">=6" } }, - "node_modules/q": { - "version": "1.5.1", - "license": "MIT", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, "node_modules/qs": { - "version": "6.5.2", - "license": "BSD-3-Clause", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "engines": { "node": ">=0.6" } @@ -4205,8 +4218,9 @@ } }, "node_modules/source-map": { - "version": "0.7.3", - "license": "BSD-3-Clause", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "engines": { "node": ">= 8" } @@ -4375,10 +4389,6 @@ "node": ">=0.8" } }, - "node_modules/traverse-chain": { - "version": "0.1.0", - "license": "MIT" - }, "node_modules/ts-node": { "version": "10.4.0", "dev": true, @@ -4517,6 +4527,12 @@ "node": ">=4.2.0" } }, + "node_modules/undent": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/undent/-/undent-0.1.0.tgz", + "integrity": "sha512-vohX7ywgBjRxDNw+f3wHclSXmO0z9HsEfmGObOuG7G0yi7kZ6OtCG8kAxtDSNklmua5KR6ev2drTFqMGqpYEbg==", + "dev": true + }, "node_modules/universalify": { "version": "0.1.2", "license": "MIT", @@ -4833,13 +4849,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "json5": { - "version": "2.2.0", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, "semver": { "version": "6.3.0", "dev": true @@ -5228,20 +5237,14 @@ "@types/node": "*" } }, - "@types/glob": { - "version": "7.2.0", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, "@types/json-schema": { "version": "7.0.9", "dev": true }, - "@types/minimatch": { - "version": "3.0.5", + "@types/line-column": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/line-column/-/line-column-1.0.0.tgz", + "integrity": "sha512-wbw+IDRw/xY/RGy+BL6f4Eey4jsUgHQrMuA4Qj0CSG3x/7C2Oc57pmRoM2z3M4DkylWRz+G1pfX06sCXQm0J+w==", "dev": true }, "@types/mocha": { @@ -5786,6 +5789,14 @@ "wrap-ansi": "^6.2.0" } }, + "clone-regexp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", + "requires": { + "is-regexp": "^2.0.0" + } + }, "color-convert": { "version": "2.0.1", "requires": { @@ -6166,12 +6177,6 @@ "to-regex-range": "^5.0.1" } }, - "find": { - "version": "0.1.7", - "requires": { - "traverse-chain": "~0.1.0" - } - }, "find-cache-dir": { "version": "3.3.2", "dev": true, @@ -6190,13 +6195,6 @@ } } }, - "find-in-files": { - "version": "0.5.0", - "requires": { - "find": "^0.1.5", - "q": "^1.0.1" - } - }, "find-up": { "version": "4.1.0", "dev": true, @@ -6468,6 +6466,11 @@ "version": "2.1.0", "dev": true }, + "is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==" + }, "is-stream": { "version": "2.0.1", "dev": true @@ -6490,6 +6493,14 @@ "version": "2.0.0", "dev": true }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + }, "isstream": { "version": "0.1.2" }, @@ -6597,6 +6608,12 @@ "json-stringify-safe": { "version": "5.0.1" }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, "jsonc-parser": { "version": "2.3.1" }, @@ -6650,6 +6667,15 @@ "immediate": "~3.0.5" } }, + "line-column": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz", + "integrity": "sha1-0lryk2tvSEkXKzEuR5LR2Ye8NKI=", + "requires": { + "isarray": "^1.0.0", + "isobject": "^2.0.0" + } + }, "locate-path": { "version": "5.0.0", "dev": true, @@ -6691,7 +6717,9 @@ } }, "luxon": { - "version": "1.28.0" + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", + "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==" }, "make-dir": { "version": "3.1.0", @@ -6730,7 +6758,9 @@ } }, "minimatch": { - "version": "3.0.4", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -6882,9 +6912,9 @@ } }, "moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "ms": { "version": "2.1.2", @@ -7127,11 +7157,10 @@ "punycode": { "version": "2.1.1" }, - "q": { - "version": "1.5.1" - }, "qs": { - "version": "6.5.2" + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "queue-microtask": { "version": "1.2.3" @@ -7446,7 +7475,9 @@ "version": "4.2.0" }, "source-map": { - "version": "0.7.3" + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "source-map-support": { "version": "0.5.20", @@ -7558,9 +7589,6 @@ "punycode": "^2.1.1" } }, - "traverse-chain": { - "version": "0.1.0" - }, "ts-node": { "version": "10.4.0", "dev": true, @@ -7634,6 +7662,12 @@ "version": "4.4.4", "dev": true }, + "undent": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/undent/-/undent-0.1.0.tgz", + "integrity": "sha512-vohX7ywgBjRxDNw+f3wHclSXmO0z9HsEfmGObOuG7G0yi7kZ6OtCG8kAxtDSNklmua5KR6ev2drTFqMGqpYEbg==", + "dev": true + }, "universalify": { "version": "0.1.2" }, diff --git a/package.json b/package.json index 7e133e7c..8d249fcf 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@types/dedent": "^0.7.0", "@types/find-in-files": "^0.5.1", "@types/fs-extra": "^9.0.13", - "@types/glob": "^7.2.0", + "@types/line-column": "^1.0.0", "@types/mocha": "^9.0.0", "@types/node": "^16.11.6", "@types/request": "^2.48.7", @@ -81,16 +81,19 @@ "sinon": "^11.1.2", "source-map-support": "^0.5.20", "ts-node": "^10.4.0", - "typescript": "^4.4.4" + "typescript": "^4.4.4", + "undent": "^0.1.0" }, "dependencies": { "@rokucommunity/logger": "^0.3.0", "brighterscript": "^0.49.0", + "clone-regexp": "2.2.0", "dateformat": "^4.6.3", "eol": "^0.9.1", "eventemitter3": "^4.0.7", - "find-in-files": "^0.5.0", + "fast-glob": "^3.2.11", "fs-extra": "^10.0.0", + "line-column": "^1.0.2", "natural-orderby": "^2.0.3", "replace-in-file": "^6.3.2", "replace-last": "^1.2.6", @@ -98,7 +101,7 @@ "semver": "^7.3.5", "serialize-error": "^8.1.0", "smart-buffer": "^4.2.0", - "source-map": "^0.7.3", + "source-map": "^0.7.4", "telnet-client": "^1.4.9", "vscode-debugadapter": "^1.49.0", "vscode-debugprotocol": "^1.49.0", diff --git a/src/FileUtils.spec.ts b/src/FileUtils.spec.ts index 22667067..2f882c9d 100644 --- a/src/FileUtils.spec.ts +++ b/src/FileUtils.spec.ts @@ -2,30 +2,38 @@ import { expect } from 'chai'; import * as fsExtra from 'fs-extra'; import * as path from 'path'; import * as sinonActual from 'sinon'; - import { SourceNode } from 'source-map'; -import { fileUtils, standardizePath as s } from './FileUtils'; +import { fileUtils } from './FileUtils'; +import { standardizePath as s } from 'brighterscript'; import { SourceMapManager } from './managers/SourceMapManager'; let sinon = sinonActual.createSandbox(); -const rootDir = path.normalize(path.dirname(__dirname)); +const tempDir = s`${__dirname}/../.tmp`; +const rootDir = s`${tempDir}/rootDir}`; describe('FileUtils', () => { let sourceMapManager: SourceMapManager; beforeEach(() => { + fsExtra.emptydirSync(tempDir); sourceMapManager = new SourceMapManager(); }); afterEach(() => { + fsExtra.removeSync(tempDir); sinon.restore(); }); describe('getAllRelativePaths', () => { //basic test to get code coverage...we don't need to test the glob code too much here... it('works', async () => { - let paths = await fileUtils.getAllRelativePaths(s`${__dirname}/../src/`); + fsExtra.outputFileSync(s`${tempDir}/file.txt`, ''); + fsExtra.outputFileSync(s`${tempDir}/subdir/file.txt`, ''); + let paths = await fileUtils.getAllRelativePaths(tempDir); expect( - paths - ).to.contain(`index.ts`); + paths.sort() + ).to.eql([ + 'file.txt', + s`subdir/file.txt` + ]); }); }); diff --git a/src/FileUtils.ts b/src/FileUtils.ts index be8103bf..efaa7b82 100644 --- a/src/FileUtils.ts +++ b/src/FileUtils.ts @@ -1,10 +1,13 @@ -import * as findInFiles from 'find-in-files'; import * as fsExtra from 'fs-extra'; -import * as glob from 'glob'; +import * as fastGlob from 'fast-glob'; +import * as cloneRegexp from 'clone-regexp'; import * as path from 'path'; -import { promisify } from 'util'; import * as rokuDeploy from 'roku-deploy'; -const globp = promisify(glob); +import type { Position, Range } from 'brighterscript'; +import { standardizePath as s, util } from 'brighterscript'; +import { Cache } from 'brighterscript/dist/Cache'; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import lineColumn = require('line-column'); export class FileUtils { @@ -50,17 +53,12 @@ export class FileUtils { * @param directoryPath */ public async getAllRelativePaths(directoryPath: string) { - //normalize the path - directoryPath = this.removeTrailingSlash( - path.normalize(directoryPath) - ); - - let paths = await globp(path.join(directoryPath, '**/*')); - for (let i = 0; i < paths.length; i++) { - //make the path relative (+1 for removing the slash) - paths[i] = paths[i].substring(directoryPath.length + 1); - } - return paths; + let paths = await fastGlob('**/*', { + cwd: s`${directoryPath}` + }); + return paths + //os-normalize each path separator + .map(x => s`${x}`); } /** @@ -225,54 +223,91 @@ export class FileUtils { return result; } + /** + * Find all locations of the regex pattern in the specified files. + * @param pattern a regular expression pattern to find in all files. The global flag will be force-enabled before it's used. + */ + private async findInFiles(pattern: RegExp, cwd: string, fileGlobs: string[]) { + const filePaths = await fastGlob(fileGlobs, { + cwd: cwd, + absolute: true + }); + const results: Array<{ + srcPath: string; + range: Range; + fileContents: string; + match: RegExpExecArray; + }> = []; + await Promise.all(filePaths.map(async (filePath) => { + const fileContents = (await fsExtra.readFile(filePath)).toString(); + const finder = lineColumn(fileContents); + let match: RegExpExecArray; + const regexp = cloneRegexp(pattern, { global: true }); + while ((match = regexp.exec(fileContents))) { + const beginPosition = finder.fromIndex(match.index); + const endPosition = finder.fromIndex(match.index + match[0].length); + results.push({ + srcPath: s`${filePath}`, + //finder returns 1-based line and col values, so offset that in the Range + range: util.createRange( + beginPosition.line - 1, + beginPosition.col - 1, + endPosition.line - 1, + endPosition.col - 1 + ), + fileContents: fileContents, + match: match + }); + } + })); + results.sort((a, b) => a.srcPath.toLowerCase().localeCompare(b.srcPath.toLowerCase())); + return results; + } + /** * Given a path to a folder, search all files until an entry point is found. * (An entry point is a function that roku uses as the Main function to start the program). - * @param projectPath - a path to a Roku project + * @param rootDir - a path to the root of a Roku project. */ - public async findEntryPoint(projectPath: string) { - let results = { - - ...await findInFiles.find({ term: 'sub\\s+RunUserInterface\\s*\\(', flags: 'ig' }, projectPath, /.*\.brs/), - ...await findInFiles.find({ term: 'function\\s+RunUserInterface\\s*\\(', flags: 'ig' }, projectPath, /.*\.brs/), - ...await findInFiles.find({ term: 'sub\\s+main\\s*\\(', flags: 'ig' }, projectPath, /.*\.brs/), - ...await findInFiles.find({ term: 'function\\s+main\\s*\\(', flags: 'ig' }, projectPath, /.*\.brs/), - ...await findInFiles.find({ term: 'sub\\s+RunScreenSaver\\s*\\(', flags: 'ig' }, projectPath, /.*\.brs/), - ...await findInFiles.find({ term: 'function\\s+RunScreenSaver\\s*\\(', flags: 'ig' }, projectPath, /.*\.brs/) - }; - let keys = Object.keys(results); - if (keys.length === 0) { - throw new Error('Unable to find an entry point. Please make sure that you have a RunUserInterface, RunScreenSaver, or Main sub/function declared in your BrightScript project'); - } - - let entryPath = keys[0]; - - let entryLineContents = results[entryPath].line[0]; + public findEntryPoint(rootDir: string) { + return this.entryPointCache.getOrAdd(this.standardizePath(rootDir), async (projectPath: string) => { + projectPath = s`${projectPath}`; + let searchResults = await this.findInFiles( + /(?:sub|function)\s+(RunUserInterface|main|RunScreenSaver)\s*\(/ig, + projectPath, + ['source/**/*.brs'] + ); - let lineNumber: number; - //load the file contents - let contents = await fsExtra.readFile(entryPath); - let lines = contents.toString().split(/\r?\n/g); - //loop through the lines until we find the entry line - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - if (line.includes(entryLineContents)) { - lineNumber = i + 1; - break; + if (searchResults.length === 0) { + throw new Error('Unable to find an entry point. Please make sure that you have a RunUserInterface, RunScreenSaver, or Main sub/function declared in your BrightScript project'); } - } - let relativePath = fileUtils.removeLeadingSlash( - rokuDeploy.util.stringReplaceInsensitive(entryPath, projectPath, '') - ); - return { - relativePath: relativePath, - pathAbsolute: entryPath, - contents: entryLineContents, - lineNumber: lineNumber - }; + const [firstResult] = searchResults; + + let destPath = fileUtils.removeLeadingSlash( + rokuDeploy.util.stringReplaceInsensitive(firstResult.srcPath, projectPath, '') + ); + return { + functionName: firstResult.match[1], + srcPath: firstResult.srcPath, + destPath: destPath, + fileContents: firstResult.fileContents, + position: firstResult.range.start + }; + }); } + private entryPointCache = new Cache>(); + /** * If a string has a leading slash, remove it */ diff --git a/src/debugSession/BrightScriptDebugSession.spec.ts b/src/debugSession/BrightScriptDebugSession.spec.ts index ca3d106e..1453e920 100644 --- a/src/debugSession/BrightScriptDebugSession.spec.ts +++ b/src/debugSession/BrightScriptDebugSession.spec.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import * as assert from 'assert'; import * as fsExtra from 'fs-extra'; +import { standardizePath as s } from 'brighterscript'; import * as path from 'path'; import * as sinonActual from 'sinon'; import type { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol'; @@ -14,18 +15,25 @@ import { HighLevelType } from '../interfaces'; import type { LaunchConfiguration } from '../LaunchConfiguration'; import type { SinonStub } from 'sinon'; -let sinon = sinonActual.createSandbox(); -let cwd = fileUtils.standardizePath(process.cwd()); -let outDir = fileUtils.standardizePath(`${cwd}/outDir`); -let stagingFolderPath = fileUtils.standardizePath(`${outDir}/stagingDir`); -const rootDir = path.normalize(path.dirname(__dirname)); +const sinon = sinonActual.createSandbox(); +const tempDir = s`${__dirname}/../.tmp`; +const cwd = fileUtils.standardizePath(process.cwd()); +const outDir = fileUtils.standardizePath(`${cwd}/outDir`); +const stagingFolderPath = fileUtils.standardizePath(`${outDir}/stagingDir`); +const rootDir = s`${tempDir}/rootDir`; describe('BrightScriptDebugSession', () => { let responseDeferreds = []; let responses = []; + beforeEach(() => { + fsExtra.emptydirSync(rootDir); + fsExtra.emptydirSync(stagingFolderPath); + fsExtra.emptydirSync(outDir); + }); + afterEach(() => { - fsExtra.removeSync(outDir); + fsExtra.emptydirSync(tempDir); sinon.restore(); }); @@ -264,18 +272,15 @@ describe('BrightScriptDebugSession', () => { }); describe('findMainFunction', () => { - let folder; afterEach(() => { fsExtra.emptyDirSync('./.tmp'); fsExtra.rmdirSync('./.tmp'); }); - async function doTest(fileContents: string, lineContents: string, lineNumber: number) { - fsExtra.emptyDirSync('./.tmp'); - folder = path.resolve('./.tmp/findMainFunctionTests/'); - fsExtra.mkdirSync(folder); + async function doTest(fileContents: string, functionName: string, lineIndex: number) { + fsExtra.emptyDirSync(rootDir); - let filePath = path.resolve(`${folder}/main.brs`); + let filePath = path.resolve(`${rootDir}/source/main.brs`); //prevent actually talking to the file system...just hardcode the list to exactly our main file (session.rokuDeploy as any).getFilePaths = () => { @@ -285,56 +290,62 @@ describe('BrightScriptDebugSession', () => { }]; }; - fsExtra.writeFileSync(filePath, fileContents); + fsExtra.outputFileSync(filePath, fileContents); (session as any).launchConfiguration = { - files: [ - folder + '/**/*' - ] + rootDir: rootDir, + files: ['**/*'] }; - let entryPoint = await fileUtils.findEntryPoint(folder); - expect(entryPoint.pathAbsolute).to.equal(filePath); - expect(entryPoint.lineNumber).to.equal(lineNumber); - expect(entryPoint.contents).to.equal(lineContents); + //clear the cache so tests work + fileUtils['entryPointCache'].clear(); + let entryPoint = await fileUtils.findEntryPoint(rootDir); + expect(s`${entryPoint.srcPath}`).to.equal(s`${filePath}`); + expect(entryPoint.position.line).to.equal(lineIndex); + expect(entryPoint.functionName).to.eql(functionName); + return entryPoint; } it('works for RunUserInterface', async () => { - await doTest('\nsub RunUserInterface()\nend sub', 'sub RunUserInterface()', 2); + await doTest('\nsub RunUserInterface()\nend sub', 'RunUserInterface', 1); //works with args - await doTest('\n\nsub RunUserInterface(args as Dynamic)\nend sub', 'sub RunUserInterface(args as Dynamic)', 3); + await doTest('\n\nsub RunUserInterface(args as Dynamic)\nend sub', 'RunUserInterface', 2); //works with extra spacing - await doTest('\n\nsub RunUserInterface()\nend sub', 'sub RunUserInterface()', 3); - await doTest('\n\nsub RunUserInterface ()\nend sub', 'sub RunUserInterface ()', 3); + await doTest('\n\nsub RunUserInterface()\nend sub', 'RunUserInterface', 2); + await doTest('\n\nsub RunUserInterface ()\nend sub', 'RunUserInterface', 2); }); it('works for sub main', async () => { - await doTest('\nsub Main()\nend sub', 'sub Main()', 2); + await doTest('\nsub Main()\nend sub', 'Main', 1); //works with args - await doTest('sub Main(args as Dynamic)\nend sub', 'sub Main(args as Dynamic)', 1); + await doTest('sub Main(args as Dynamic)\nend sub', 'Main', 0); //works with extra spacing - await doTest('sub Main()\nend sub', 'sub Main()', 1); - await doTest('sub Main ()\nend sub', 'sub Main ()', 1); + await doTest('sub Main()\nend sub', 'Main', 0); + await doTest('sub Main ()\nend sub', 'Main', 0); + }); + + it('picks the top main function when there are multiples, and returns the name of the function', async () => { + await doTest(`sub main()\nend sub\nsub main()\nend sub`, `main`, 0); }); it('works for function main', async () => { - await doTest('function Main()\nend function', 'function Main()', 1); - await doTest('function Main(args as Dynamic)\nend function', 'function Main(args as Dynamic)', 1); + await doTest('function Main()\nend function', 'Main', 0); + await doTest('function Main(args as Dynamic)\nend function', 'Main', 0); //works with extra spacing - await doTest('function Main()\nend function', 'function Main()', 1); - await doTest('function Main ()\nend function', 'function Main ()', 1); + await doTest('function Main()\nend function', 'Main', 0); + await doTest('function Main ()\nend function', 'Main', 0); }); it('works for sub RunScreenSaver', async () => { - await doTest('sub RunScreenSaver()\nend sub', 'sub RunScreenSaver()', 1); + await doTest('sub RunScreenSaver()\nend sub', 'RunScreenSaver', 0); //works with extra spacing - await doTest('sub RunScreenSaver()\nend sub', 'sub RunScreenSaver()', 1); - await doTest('sub RunScreenSaver ()\nend sub', 'sub RunScreenSaver ()', 1); + await doTest('sub RunScreenSaver()\nend sub', 'RunScreenSaver', 0); + await doTest('sub RunScreenSaver ()\nend sub', 'RunScreenSaver', 0); }); it('works for function RunScreenSaver', async () => { - await doTest('function RunScreenSaver()\nend function', 'function RunScreenSaver()', 1); + await doTest('function RunScreenSaver()\nend function', 'RunScreenSaver', 0); //works with extra spacing - await doTest('function RunScreenSaver()\nend function', 'function RunScreenSaver()', 1); - await doTest('function RunScreenSaver ()\nend function', 'function RunScreenSaver ()', 1); + await doTest('function RunScreenSaver()\nend function', 'RunScreenSaver', 0); + await doTest('function RunScreenSaver ()\nend function', 'RunScreenSaver', 0); }); }); diff --git a/src/managers/LocationManager.ts b/src/managers/LocationManager.ts index 70993706..d84b8a6f 100644 --- a/src/managers/LocationManager.ts +++ b/src/managers/LocationManager.ts @@ -2,7 +2,7 @@ import * as fsExtra from 'fs-extra'; import * as path from 'path'; import { standardizePath as s, fileUtils } from '../FileUtils'; import type { SourceMapManager } from './SourceMapManager'; -import * as glob from 'glob'; +import * as fastGlob from 'fast-glob'; /** * Find original source locations based on debugger/staging locations. @@ -114,7 +114,7 @@ export class LocationManager { //look through the sourcemaps in the staging folder for any instances of this source location let locations = await this.sourceMapManager.getGeneratedLocations( - glob.sync('**/*.map', { + await fastGlob('**/*.map', { cwd: stagingFolderPath, absolute: true }), diff --git a/src/managers/ProjectManager.ts b/src/managers/ProjectManager.ts index 12efb00b..58caa6b1 100644 --- a/src/managers/ProjectManager.ts +++ b/src/managers/ProjectManager.ts @@ -3,9 +3,7 @@ import * as fsExtra from 'fs-extra'; import * as path from 'path'; import * as rokuDeploy from 'roku-deploy'; import type { FileEntry } from 'roku-deploy'; -import * as glob from 'glob'; -import { promisify } from 'util'; -const globAsync = promisify(glob); +import * as fastGlob from 'fast-glob'; import type { BreakpointManager } from './BreakpointManager'; import { fileUtils, standardizePath as s } from '../FileUtils'; import type { LocationManager, SourceLocation } from './LocationManager'; @@ -143,7 +141,7 @@ export class ProjectManager { let entryPoint = await fileUtils.findEntryPoint(stagingFolderPath); //convert entry point staging location to source location - let sourceLocation = await this.getSourceLocation(entryPoint.relativePath, entryPoint.lineNumber); + let sourceLocation = await this.getSourceLocation(entryPoint.destPath, entryPoint.position.line + 1); //register the entry breakpoint this.breakpointManager.registerBreakpoint(sourceLocation.filePath, { @@ -392,6 +390,45 @@ export class Project { } } + /** + * Find the line where the `scene.show()` function is called (if possible). + * This does the following: + * - finds the entryPoint function + * - scan the function to find the `createObject("roSGScreen")` call and note its variable + * - find where the screen variable's `show()` method is called + */ + // private async findSceneShow() { + // const entryPoint = await fileUtils.findEntryPoint(this.rootDir); + // const lowerFunctionName = entryPoint.functionName?.toLowerCase(); + // const ast = Parser.parse( + // entryPoint.fileContents + // ); + // const entryFunc = ast.statements.find(x => (x as FunctionStatement)?.name?.text?.toLowerCase() === lowerFunctionName) as FunctionStatement; + // if (entryFunc) { + // const finder = lineColumn(entryPoint.fileContents); + // const range = entryFunc.func.body.range; + // const startIndex = finder.toIndex(range.start.line, range.start.character); + // const stopIndex = finder.toIndex(range.end.line, range.end.character); + // const functionBody = entryPoint.fileContents.substring(startIndex, stopIndex); + + // const [, sceneVariable] = /([a-z0-9_\[\]"]+)\s*=\s*createObject\s*\(\s*"roSGScreen"\s*)/i.exec(functionBody); + // if (sceneVariable) { + // const regexp = new RegExp(`\\b${sceneVariable}\\s*\\.\\s*show\\(.*\\)`, 'i'); + // const match = regexp.exec(functionBody); + // const startPosition = finder.fromIndex(startIndex + match.index); + // const stopPosition = finder.fromIndex(startIndex + match.index + match[0].length); + // return { + // range: bscUtil.createRange( + // startPosition.line, + // startPosition.col, + // stopPosition.line, + // stopPosition.col + // ) + // }; + // } + // } + // } + public static RDB_ODC_NODE_CODE = `if true = CreateObject("roAppInfo").IsDev() then m.vscode_rdb_odc_node = createObject("roSGNode", "RTA_OnDeviceComponent") ' RDB OnDeviceComponent`; public static RDB_ODC_ENTRY = 'vscode_rdb_on_device_component_entry'; /** @@ -403,10 +440,10 @@ export class Project { return; } try { - let files = await globAsync(`${this.rdbFilesBasePath}/**/*`, { + let files = await fastGlob(`${this.rdbFilesBasePath}/**/*`, { cwd: './', absolute: false, - follow: true + followSymbolicLinks: true }); for (let filePathAbsolute of files) { const promises = []; From e206122809afb0238efdae36b639beeae1441579 Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 8 Jul 2024 08:39:28 -0400 Subject: [PATCH 2/2] Committing what I have. This is not functional --- package-lock.json | 116 ++++++++++++++++++--------- package.json | 2 +- src/managers/ProjectManager.spec.ts | 32 ++++++++ src/managers/ProjectManager.ts | 117 ++++++++++++++++++++-------- 4 files changed, 196 insertions(+), 71 deletions(-) diff --git a/package-lock.json b/package-lock.json index f455c655..9f9f7162 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@rokucommunity/logger": "^0.3.0", - "brighterscript": "^0.49.0", + "brighterscript": "^0.61.3", "clone-regexp": "2.2.0", "dateformat": "^4.6.3", "eol": "^0.9.1", @@ -1153,9 +1153,9 @@ } }, "node_modules/brighterscript": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.49.0.tgz", - "integrity": "sha512-B4xVu6O6+F2FhHSa+dtRs18krUitp7bRm25xpiHsnSNg+G+mxmh9ALbd0iNX9PCPstknEVO9uC6dsDF43XcOEg==", + "version": "0.61.3", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.3.tgz", + "integrity": "sha512-8BDpOSCdmkS/QcTdPTUW/99nCBypuoa/Zz6PZHI6OiVylqTBidtrGI7lBZotqY6yvQ3KJl24thhLHK5XuIT/6w==", "dependencies": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -1167,19 +1167,20 @@ "cross-platform-clear-console": "^2.3.0", "debounce-promise": "^3.1.0", "eventemitter3": "^4.0.0", + "fast-glob": "^3.2.11", "file-url": "^3.0.0", - "fs-extra": "^7.0.1", - "glob": "^7.1.6", + "fs-extra": "^8.0.0", "jsonc-parser": "^2.3.0", "long": "^3.2.0", - "luxon": "^1.8.3", + "luxon": "^2.5.2", "minimatch": "^3.0.4", "moment": "^2.23.0", "p-settle": "^2.1.0", "parse-ms": "^2.1.0", - "roku-deploy": "^3.5.4", + "require-relative": "^0.8.7", + "roku-deploy": "^3.9.3", "serialize-error": "^7.0.1", - "source-map": "^0.7.3", + "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", "vscode-languageserver-protocol": "3.16.0", "vscode-languageserver-textdocument": "^1.0.1", @@ -1234,10 +1235,11 @@ "license": "MIT" }, "node_modules/brighterscript/node_modules/fs-extra": { - "version": "7.0.1", - "license": "MIT", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dependencies": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" }, @@ -1657,6 +1659,11 @@ "node": "*" } }, + "node_modules/dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + }, "node_modules/debounce-promise": { "version": "3.1.2", "license": "MIT" @@ -3017,11 +3024,11 @@ } }, "node_modules/luxon": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", - "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.2.tgz", + "integrity": "sha512-Yg7/RDp4nedqmLgyH0LwgGRvMEKVzKbUdkBYyCosbHgJ+kaOUx0qzSiSatVc3DFygnirTPYnMM2P5dg2uH1WvA==", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/make-dir": { @@ -3920,6 +3927,11 @@ "dev": true, "license": "ISC" }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==" + }, "node_modules/resolve-from": { "version": "4.0.0", "dev": true, @@ -3974,21 +3986,23 @@ } }, "node_modules/roku-deploy": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.6.0.tgz", - "integrity": "sha512-kfgM/EOhM/X1wXOx+8hGCIH9o/eu5Vtw8qmhnHA0EIIyRhHQPy+W4iKcab6fPbibLKo4f0V47GKO1TTKNR+6kA==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.3.tgz", + "integrity": "sha512-cjTx5ffZNt07rQS+0s2sTBHkZKUk283y9f6UnbI77X03lQ60vYlCnqsKswWisFYMHPIdvsTLLSfKsshAPwKHEQ==", "dependencies": { "chalk": "^2.4.2", "dateformat": "^3.0.3", + "dayjs": "^1.11.0", "fast-glob": "^3.2.11", "fs-extra": "^7.0.1", "is-glob": "^4.0.3", "jsonc-parser": "^2.3.0", "jszip": "^3.6.0", - "minimatch": "^3.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", + "picomatch": "^2.2.1", "request": "^2.88.0", + "temp-dir": "^2.0.0", "xml2js": "^0.4.23" }, "bin": { @@ -4342,6 +4356,14 @@ "url": "https://paypal.me/kozjak" } }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "dev": true, @@ -5540,9 +5562,9 @@ } }, "brighterscript": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.49.0.tgz", - "integrity": "sha512-B4xVu6O6+F2FhHSa+dtRs18krUitp7bRm25xpiHsnSNg+G+mxmh9ALbd0iNX9PCPstknEVO9uC6dsDF43XcOEg==", + "version": "0.61.3", + "resolved": "https://registry.npmjs.org/brighterscript/-/brighterscript-0.61.3.tgz", + "integrity": "sha512-8BDpOSCdmkS/QcTdPTUW/99nCBypuoa/Zz6PZHI6OiVylqTBidtrGI7lBZotqY6yvQ3KJl24thhLHK5XuIT/6w==", "requires": { "@rokucommunity/bslib": "^0.1.1", "@xml-tools/parser": "^1.0.7", @@ -5554,19 +5576,20 @@ "cross-platform-clear-console": "^2.3.0", "debounce-promise": "^3.1.0", "eventemitter3": "^4.0.0", + "fast-glob": "^3.2.11", "file-url": "^3.0.0", - "fs-extra": "^7.0.1", - "glob": "^7.1.6", + "fs-extra": "^8.0.0", "jsonc-parser": "^2.3.0", "long": "^3.2.0", - "luxon": "^1.8.3", + "luxon": "^2.5.2", "minimatch": "^3.0.4", "moment": "^2.23.0", "p-settle": "^2.1.0", "parse-ms": "^2.1.0", - "roku-deploy": "^3.5.4", + "require-relative": "^0.8.7", + "roku-deploy": "^3.9.3", "serialize-error": "^7.0.1", - "source-map": "^0.7.3", + "source-map": "^0.7.4", "vscode-languageserver": "7.0.0", "vscode-languageserver-protocol": "3.16.0", "vscode-languageserver-textdocument": "^1.0.1", @@ -5607,9 +5630,11 @@ "version": "1.1.3" }, "fs-extra": { - "version": "7.0.1", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } @@ -5865,6 +5890,11 @@ "dateformat": { "version": "4.6.3" }, + "dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + }, "debounce-promise": { "version": "3.1.2" }, @@ -6717,9 +6747,9 @@ } }, "luxon": { - "version": "1.28.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz", - "integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.5.2.tgz", + "integrity": "sha512-Yg7/RDp4nedqmLgyH0LwgGRvMEKVzKbUdkBYyCosbHgJ+kaOUx0qzSiSatVc3DFygnirTPYnMM2P5dg2uH1WvA==" }, "make-dir": { "version": "3.1.0", @@ -7285,6 +7315,11 @@ "version": "2.0.0", "dev": true }, + "require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==" + }, "resolve-from": { "version": "4.0.0", "dev": true @@ -7320,21 +7355,23 @@ } }, "roku-deploy": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.6.0.tgz", - "integrity": "sha512-kfgM/EOhM/X1wXOx+8hGCIH9o/eu5Vtw8qmhnHA0EIIyRhHQPy+W4iKcab6fPbibLKo4f0V47GKO1TTKNR+6kA==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.9.3.tgz", + "integrity": "sha512-cjTx5ffZNt07rQS+0s2sTBHkZKUk283y9f6UnbI77X03lQ60vYlCnqsKswWisFYMHPIdvsTLLSfKsshAPwKHEQ==", "requires": { "chalk": "^2.4.2", "dateformat": "^3.0.3", + "dayjs": "^1.11.0", "fast-glob": "^3.2.11", "fs-extra": "^7.0.1", "is-glob": "^4.0.3", "jsonc-parser": "^2.3.0", "jszip": "^3.6.0", - "minimatch": "^3.0.4", "moment": "^2.29.1", "parse-ms": "^2.1.0", + "picomatch": "^2.2.1", "request": "^2.88.0", + "temp-dir": "^2.0.0", "xml2js": "^0.4.23" }, "dependencies": { @@ -7559,6 +7596,11 @@ "bluebird": "^3.5.4" } }, + "temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==" + }, "test-exclude": { "version": "6.0.0", "dev": true, diff --git a/package.json b/package.json index 8d249fcf..166f47ca 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ }, "dependencies": { "@rokucommunity/logger": "^0.3.0", - "brighterscript": "^0.49.0", + "brighterscript": "^0.61.3", "clone-regexp": "2.2.0", "dateformat": "^4.6.3", "eol": "^0.9.1", diff --git a/src/managers/ProjectManager.spec.ts b/src/managers/ProjectManager.spec.ts index 8fe1b2e4..3d998253 100644 --- a/src/managers/ProjectManager.spec.ts +++ b/src/managers/ProjectManager.spec.ts @@ -744,4 +744,36 @@ describe('ComponentLibraryProject', () => { expect(project.removeFileNamePostfix(`source/__lib1.brs/main.brs`)).to.equal('source/__lib1.brs/main.brs'); }); }); + + describe('findSceneShow', () => { + it('finds simple case', () => { + fsExtra.outputFileSync(`${rootDir}/source/main.brs`, ` + sub Main(inputArguments as object) + screen = createObject("roSGScreen") + m.port = createObject("roMessagePort") + screen.setMessagePort(m.port) + scene = screen.CreateScene("MainScene") + screen.show() : initThing() + scene.observeField("appExit", m.port) + scene.setFocus(true) + + while true + msg = wait(0, m.port) + msgType = type(msg) + + if msgType = "roSGScreenEvent" then + if msg.isScreenClosed() then + return + else if msgType = "roSGNodeEvent" then + field = msg.getField() + if field = "appExit" then + return + end if + end if + end if + end while + end sub + `); + }); + }); }); diff --git a/src/managers/ProjectManager.ts b/src/managers/ProjectManager.ts index 58caa6b1..4bf0bb2b 100644 --- a/src/managers/ProjectManager.ts +++ b/src/managers/ProjectManager.ts @@ -9,6 +9,8 @@ import { fileUtils, standardizePath as s } from '../FileUtils'; import type { LocationManager, SourceLocation } from './LocationManager'; import { util } from '../util'; import { logger } from '../logging'; +import { AssignmentStatement, CancellationTokenSource, DottedSetStatement, FunctionStatement, IndexedSetStatement, isAssignmentStatement, isCallExpression, isDottedGetExpression, isDottedSetStatement, isFunctionStatement, isIndexedSetStatement, isVariableExpression, Parser, WalkMode } from 'brighterscript'; +import lineColumn = require('line-column'); // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports const replaceInFile = require('replace-in-file'); @@ -394,40 +396,89 @@ export class Project { * Find the line where the `scene.show()` function is called (if possible). * This does the following: * - finds the entryPoint function - * - scan the function to find the `createObject("roSGScreen")` call and note its variable - * - find where the screen variable's `show()` method is called + * - scan the function to find the `screen = createObject("roSGScreen")` call and note its variable + * - find where the screen variable's `show()` method is called (i.e. `screen.show()`) */ - // private async findSceneShow() { - // const entryPoint = await fileUtils.findEntryPoint(this.rootDir); - // const lowerFunctionName = entryPoint.functionName?.toLowerCase(); - // const ast = Parser.parse( - // entryPoint.fileContents - // ); - // const entryFunc = ast.statements.find(x => (x as FunctionStatement)?.name?.text?.toLowerCase() === lowerFunctionName) as FunctionStatement; - // if (entryFunc) { - // const finder = lineColumn(entryPoint.fileContents); - // const range = entryFunc.func.body.range; - // const startIndex = finder.toIndex(range.start.line, range.start.character); - // const stopIndex = finder.toIndex(range.end.line, range.end.character); - // const functionBody = entryPoint.fileContents.substring(startIndex, stopIndex); - - // const [, sceneVariable] = /([a-z0-9_\[\]"]+)\s*=\s*createObject\s*\(\s*"roSGScreen"\s*)/i.exec(functionBody); - // if (sceneVariable) { - // const regexp = new RegExp(`\\b${sceneVariable}\\s*\\.\\s*show\\(.*\\)`, 'i'); - // const match = regexp.exec(functionBody); - // const startPosition = finder.fromIndex(startIndex + match.index); - // const stopPosition = finder.fromIndex(startIndex + match.index + match[0].length); - // return { - // range: bscUtil.createRange( - // startPosition.line, - // startPosition.col, - // stopPosition.line, - // stopPosition.col - // ) - // }; - // } - // } - // } + private async findSceneShow() { + const entryPoint = await fileUtils.findEntryPoint(this.rootDir); + const lowerFunctionName = entryPoint.functionName?.toLowerCase(); + const { ast } = Parser.parse( + entryPoint.fileContents + ); + + const entryFunc = ast.findChild(node => { + return isFunctionStatement(node) && node.name.text.toLowerCase() === lowerFunctionName; + }, { + walkMode: WalkMode.visitStatements + }); + + if (!entryFunc) { + return; + } + + //find the "screen.createScene()" call + const cancel = new CancellationTokenSource(); + const sceneVar = entryFunc.findChild((node) => { + //find a function call in this format: `something.CreateScene(` + if (isCallExpression(node) && isDottedGetExpression(node.callee) && node.callee.name.text?.toLowerCase() === 'createscene') { + //walk upwards until we find an assignment, dotted set, or indexed set + const result = node.findAncestor((ancestor) => { + if (isAssignmentStatement(ancestor)) { + return true; + } + }) as AssignmentStatement; + + if (result) { + return result; + } else { + cancel.cancel(); + } + } + }, { + walkMode: WalkMode.visitAll, + cancel: cancel.token + }); + + if (!sceneVar) { + return; + } + + //now look for `${sceneVar}.show()` + entryFunc.findChild((node) => { + if ( + isCallExpression(node) && + isDottedGetExpression(node.callee) && + node.callee.name?.text?.toLowerCase() === 'show' && + node.callee. + ) { + return; + } + }); + + if (entryFunc) { + const finder = lineColumn(entryPoint.fileContents); + const range = entryFunc.func.body.range; + const startIndex = finder.toIndex(range.start.line, range.start.character); + const stopIndex = finder.toIndex(range.end.line, range.end.character); + const functionBody = entryPoint.fileContents.substring(startIndex, stopIndex); + + const [, sceneVariable] = /([a-z0-9_\[\]"]+)\s*=\s*createObject\s*\(\s*"roSGScreen"\s*)/i.exec(functionBody); + if (sceneVariable) { + const regexp = new RegExp(`\\b${sceneVariable}\\s*\\.\\s*show\\(.*\\)`, 'i'); + const match = regexp.exec(functionBody); + const startPosition = finder.fromIndex(startIndex + match.index); + const stopPosition = finder.fromIndex(startIndex + match.index + match[0].length); + return { + range: bscUtil.createRange( + startPosition.line, + startPosition.col, + stopPosition.line, + stopPosition.col + ) + }; + } + } + } public static RDB_ODC_NODE_CODE = `if true = CreateObject("roAppInfo").IsDev() then m.vscode_rdb_odc_node = createObject("roSGNode", "RTA_OnDeviceComponent") ' RDB OnDeviceComponent`; public static RDB_ODC_ENTRY = 'vscode_rdb_on_device_component_entry';