diff --git a/package-lock.json b/package-lock.json index eef0c10..ca9f312 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "bsc-plugin-auto-findnode", "version": "0.1.0", "license": "ISC", + "dependencies": { + "minimatch": "^10.0.1" + }, "devDependencies": { "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", @@ -427,6 +430,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "8.56.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", @@ -450,6 +465,18 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1393,8 +1420,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -1505,6 +1531,18 @@ "bsc": "dist/cli.js" } }, + "node_modules/brighterscript/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/brotli": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", @@ -2445,6 +2483,18 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -2628,6 +2678,18 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3165,6 +3227,18 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -4402,15 +4476,25 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" } }, "node_modules/minimist": { @@ -6113,6 +6197,18 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7077,6 +7173,17 @@ "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "@eslint/js": { @@ -7094,6 +7201,17 @@ "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "@humanwhocodes/module-importer": { @@ -7784,8 +7902,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -7871,6 +7988,17 @@ "vscode-uri": "^2.1.1", "xml2js": "^0.5.0", "yargs": "^16.2.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "brotli": { @@ -8555,6 +8683,15 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8666,6 +8803,15 @@ "esutils": "^2.0.2" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -9087,6 +9233,17 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "glob-parent": { @@ -9996,12 +10153,21 @@ } }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + } } }, "minimist": { @@ -11275,6 +11441,17 @@ "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } }, "text-table": { diff --git a/package.json b/package.json index b52c997..2648874 100644 --- a/package.json +++ b/package.json @@ -70,5 +70,8 @@ "ts-node": "^10.9.2", "typescript": "^4.7.2", "undent": "^0.1.0" + }, + "dependencies": { + "minimatch": "^10.0.1" } } diff --git a/src/findNodes.ts b/src/findNodes.ts index b066510..180f831 100644 --- a/src/findNodes.ts +++ b/src/findNodes.ts @@ -1,6 +1,8 @@ -import type { AstEditor, FunctionStatement, BrsFile, Program, TranspileObj, BscFile, Statement, Range } from 'brighterscript'; +import { match } from 'assert'; +import type { AstEditor, FunctionStatement, BrsFile, Program, TranspileObj, BscFile, Statement, Range, XmlScope } from 'brighterscript'; import { isBrsFile, Parser, isXmlScope, DiagnosticSeverity, createVisitor, WalkMode, isDottedGetExpression, isVariableExpression, isLiteralString, util } from 'brighterscript'; import type { SGNode } from 'brighterscript/dist/parser/SGTypes'; +import * as minimatch from 'minimatch'; function findChildrenWithIDs(children: Array): Map { let foundIDs = new Map(); @@ -16,112 +18,133 @@ function findChildrenWithIDs(children: Array): Map { return foundIDs; } +function getFilteredScopes(program: Program) { + const excludeScopes = program.options?.autoFindNode?.excludeFiles || []; + let scopes: XmlScope[] = []; + + const hasExclusions = excludeScopes.length > 0; + + for (const filteredScope of program.getScopes().filter((scope) => isXmlScope(scope))) { + if (!hasExclusions) { + scopes.push(filteredScope); + } else { + let isExcluded = excludeScopes.some((pattern: string) => + minimatch.match([filteredScope.name], pattern).length > 0 + ); + if (!isExcluded) { + scopes.push(filteredScope); + } + } + } + + return scopes; +} + + export function findNodeWithIDInjection(program: Program, entries: TranspileObj[], editor: AstEditor, createdFiles: BscFile[]) { - for (const scope of program.getScopes()) { - if (isXmlScope(scope)) { - const xmlFile = scope.xmlFile; - const ids = findChildrenWithIDs(xmlFile.parser.ast.component?.children?.children ?? []); - if (ids.size > 0) { - const scopeFiles: BscFile[] = scope.getOwnFiles(); - - //find an init function from all the scope's files - let initFunction: FunctionStatement | undefined; - - let hasBrsFile = false; - for (const file of scopeFiles) { - if (isBrsFile(file)) { - hasBrsFile = true; - initFunction = file.parser.references.functionStatementLookup.get('init'); - if (initFunction) { - break; - } - } - } + let scopes = getFilteredScopes(program); + for (const scope of scopes) { + const xmlFile = scope.xmlFile; + const ids = findChildrenWithIDs(xmlFile.parser.ast.component?.children?.children ?? []); + if (ids.size > 0) { + const scopeFiles: BscFile[] = scope.getOwnFiles(); - if (!hasBrsFile) { - createdFiles.push(program.setFile(xmlFile.pkgPath.replace('.xml', '.bs'), '')); - } + //find an init function from all the scope's files + let initFunction: FunctionStatement | undefined; - //create an init function if it's missing - if (!initFunction) { - const codeBehindFile = program.getFiles(xmlFile.possibleCodebehindPkgPaths).find(x => !!x); - initFunction = Parser.parse(`sub init()\nend sub`).statements[0] as FunctionStatement; - if (codeBehindFile) { - editor.arrayPush(codeBehindFile.parser.statements, initFunction); + let hasBrsFile = false; + for (const file of scopeFiles) { + if (isBrsFile(file)) { + hasBrsFile = true; + initFunction = file.parser.references.functionStatementLookup.get('init'); + if (initFunction) { + break; } } + } + + if (!hasBrsFile) { + createdFiles.push(program.setFile(xmlFile.pkgPath.replace('.xml', '.bs'), '')); + } - if (initFunction) { - //add m variables for every xml component that has an id - // eslint-disable-next-line max-statements-per-line, @typescript-eslint/brace-style - const assignments = Array.from(ids).map(([id, range]) => { return `m.${id} = m.top.findNode("${id}")`; }).join('\n'); - const statements = (Parser.parse(` - sub temp() - ${assignments} - end sub - `).statements[0] as FunctionStatement).func.body.statements; - //add the assignments to the top of the init function - editor.arrayUnshift(initFunction.func.body.statements, ...statements); + //create an init function if it's missing + if (!initFunction) { + const codeBehindFile = program.getFiles(xmlFile.possibleCodebehindPkgPaths).find(x => !!x); + initFunction = Parser.parse(`sub init()\nend sub`).statements[0] as FunctionStatement; + if (codeBehindFile) { + editor.arrayPush(codeBehindFile.parser.statements, initFunction); } } + + if (initFunction) { + //add m variables for every xml component that has an id + // eslint-disable-next-line max-statements-per-line, @typescript-eslint/brace-style + const assignments = Array.from(ids).map(([id, range]) => { return `m.${id} = m.top.findNode("${id}")`; }).join('\n'); + const statements = (Parser.parse(` + sub temp() + ${assignments} + end sub + `).statements[0] as FunctionStatement).func.body.statements; + //add the assignments to the top of the init function + editor.arrayUnshift(initFunction.func.body.statements, ...statements); + } } } } export function validateNodeWithIDInjection(program: Program) { - for (const scope of program.getScopes()) { - if (isXmlScope(scope)) { - const xmlFile = scope.xmlFile; - const ids = findChildrenWithIDs(xmlFile.parser.ast.component?.children?.children ?? []); - if (ids.size > 0) { - const scopeFiles: BscFile[] = scope.getOwnFiles(); - - let initFunction: FunctionStatement | undefined; - let initFunctionFile: BscFile | undefined; - - for (const file of scopeFiles) { - if (isBrsFile(file)) { - initFunction = file.parser.references.functionStatementLookup.get('init'); - if (initFunction) { - initFunctionFile = file; - break; - } + let scopes = getFilteredScopes(program); + for (const scope of scopes) { + const xmlFile = scope.xmlFile; + const ids = findChildrenWithIDs(xmlFile.parser.ast.component?.children?.children ?? []); + if (ids.size > 0) { + const scopeFiles: BscFile[] = scope.getOwnFiles(); + + let initFunction: FunctionStatement | undefined; + let initFunctionFile: BscFile | undefined; + + for (const file of scopeFiles) { + if (isBrsFile(file)) { + initFunction = file.parser.references.functionStatementLookup.get('init'); + if (initFunction) { + initFunctionFile = file; + break; } } + } - if (initFunction && initFunctionFile) { - initFunction.func.body.walk(createVisitor({ - CallExpression: (expression) => { - if ( - isDottedGetExpression(expression.callee) && - expression.callee.name.text.toLocaleLowerCase() === 'findnode' && - isDottedGetExpression(expression.callee.obj) && - expression.callee.obj.name.text.toLocaleLowerCase() === 'top' && - isVariableExpression(expression.callee.obj.obj) && - expression.callee.obj.obj.name.text.toLocaleLowerCase() === 'm' && - isLiteralString(expression.args[0]) - ) { - let id = expression.args[0].token.text.replace(/^"/, '').replace(/"$/, ''); - let warningRange = ids.get(id); - if (warningRange !== undefined) { - initFunctionFile!.diagnostics.push({ - file: initFunctionFile!, - range: expression.range, - severity: DiagnosticSeverity.Warning, - message: `Unnecessary call to 'm.top.findNode("${id}")'`, - relatedInformation: [{ - message: `In scope '${scope.name}'`, - location: util.createLocation( - util.pathToUri(xmlFile.srcPath), - warningRange - ) - }] - }); - } + if (initFunction && initFunctionFile) { + initFunction.func.body.walk(createVisitor({ + CallExpression: (expression) => { + if ( + isDottedGetExpression(expression.callee) && + expression.callee.name.text.toLocaleLowerCase() === 'findnode' && + isDottedGetExpression(expression.callee.obj) && + expression.callee.obj.name.text.toLocaleLowerCase() === 'top' && + isVariableExpression(expression.callee.obj.obj) && + expression.callee.obj.obj.name.text.toLocaleLowerCase() === 'm' && + isLiteralString(expression.args[0]) + ) { + let id = expression.args[0].token.text.replace(/^"/, '').replace(/"$/, ''); + let warningRange = ids.get(id); + if (warningRange !== undefined) { + initFunctionFile!.diagnostics.push({ + file: initFunctionFile!, + range: expression.range, + severity: DiagnosticSeverity.Warning, + message: `Unnecessary call to 'm.top.findNode("${id}")'`, + relatedInformation: [{ + message: `In scope '${scope.name}'`, + location: util.createLocation( + util.pathToUri(xmlFile.srcPath), + warningRange + ) + }] + }); } } - }), { walkMode: WalkMode.visitExpressions }); - } + } + }), { walkMode: WalkMode.visitExpressions }); } } }