@forge/lint

6.0.0-next.116.0.0-next.12
out/lint/linters/app-managed-permissions-sdk-linter/detect-permissions-sdk-usage.js
+out/lint/linters/app-managed-permissions-sdk-linter/detect-permissions-sdk-usage.jsNew file
+162
Index: package/out/lint/linters/app-managed-permissions-sdk-linter/detect-permissions-sdk-usage.js
===================================================================
--- package/out/lint/linters/app-managed-permissions-sdk-linter/detect-permissions-sdk-usage.js
+++ package/out/lint/linters/app-managed-permissions-sdk-linter/detect-permissions-sdk-usage.js
@@ -0,0 +1,162 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.doesAstUsePermissionsSdk = void 0;
+const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
+const FORGE_API_MODULE = '@forge/api';
+const FORGE_BRIDGE_MODULE = '@forge/bridge';
+const FORGE_REACT_MODULE = '@forge/react';
+const PERMISSIONS_SDK_METHODS = new Set(['hasPermission', 'hasScope', 'canFetchFrom', 'canLoadResource']);
+const BRIDGE_SDK_IMPORTS = new Set(['checkPermissions', 'createPermissionUtils']);
+const getImportSource = (node) => {
+    if (node.source.type === typescript_estree_1.AST_NODE_TYPES.Literal && typeof node.source.value === 'string') {
+        return node.source.value;
+    }
+    return null;
+};
+const collectImportBindings = (program) => {
+    const forgeApiPermissions = new Set();
+    const forgeApiNamespaces = new Set();
+    const forgeBridgeSdk = new Set();
+    const forgeBridgeNamespaces = new Set();
+    const forgeReactUsePermissions = new Set();
+    const forgeReactNamespaces = new Set();
+    for (const stmt of program.body) {
+        if (stmt.type !== typescript_estree_1.AST_NODE_TYPES.ImportDeclaration)
+            continue;
+        const source = getImportSource(stmt);
+        if (!source)
+            continue;
+        if (source === FORGE_API_MODULE) {
+            for (const spec of stmt.specifiers) {
+                if (spec.type === typescript_estree_1.AST_NODE_TYPES.ImportSpecifier) {
+                    if (spec.imported.type === typescript_estree_1.AST_NODE_TYPES.Identifier && spec.imported.name === 'permissions') {
+                        forgeApiPermissions.add(spec.local.name);
+                    }
+                }
+                else if (spec.type === typescript_estree_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
+                    forgeApiNamespaces.add(spec.local.name);
+                }
+            }
+        }
+        else if (source === FORGE_BRIDGE_MODULE) {
+            for (const spec of stmt.specifiers) {
+                if (spec.type === typescript_estree_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
+                    forgeBridgeNamespaces.add(spec.local.name);
+                }
+                else if (spec.type === typescript_estree_1.AST_NODE_TYPES.ImportSpecifier && spec.imported.type === typescript_estree_1.AST_NODE_TYPES.Identifier) {
+                    if (BRIDGE_SDK_IMPORTS.has(spec.imported.name)) {
+                        forgeBridgeSdk.add(spec.local.name);
+                    }
+                }
+            }
+        }
+        else if (source === FORGE_REACT_MODULE) {
+            for (const spec of stmt.specifiers) {
+                if (spec.type === typescript_estree_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
+                    forgeReactNamespaces.add(spec.local.name);
+                }
+                else if (spec.type === typescript_estree_1.AST_NODE_TYPES.ImportSpecifier && spec.imported.type === typescript_estree_1.AST_NODE_TYPES.Identifier) {
+                    if (spec.imported.name === 'usePermissions') {
+                        forgeReactUsePermissions.add(spec.local.name);
+                    }
+                }
+            }
+        }
+    }
+    return {
+        forgeApiPermissions,
+        forgeApiNamespaces,
+        forgeBridgeSdk,
+        forgeBridgeNamespaces,
+        forgeReactUsePermissions,
+        forgeReactNamespaces
+    };
+};
+const flattenMemberExpression = (expr) => {
+    const chain = [];
+    let current = expr;
+    while (current.type === typescript_estree_1.AST_NODE_TYPES.MemberExpression) {
+        if (current.property.type !== typescript_estree_1.AST_NODE_TYPES.Identifier) {
+            return undefined;
+        }
+        chain.unshift(current.property.name);
+        current = current.object;
+    }
+    return { root: current, chain };
+};
+const isForgeApiPermissionsCall = (callee, bindings) => {
+    const flat = flattenMemberExpression(callee);
+    if (!flat)
+        return false;
+    const { root, chain } = flat;
+    if (root.type !== typescript_estree_1.AST_NODE_TYPES.Identifier) {
+        return false;
+    }
+    if (chain.length === 1 && PERMISSIONS_SDK_METHODS.has(chain[0])) {
+        return bindings.forgeApiPermissions.has(root.name);
+    }
+    if (chain.length === 2 &&
+        chain[0] === 'permissions' &&
+        PERMISSIONS_SDK_METHODS.has(chain[1]) &&
+        bindings.forgeApiNamespaces.has(root.name)) {
+        return true;
+    }
+    return false;
+};
+const isForgeBridgeSdkCall = (callee, bindings) => {
+    const flat = flattenMemberExpression(callee);
+    if (!flat)
+        return false;
+    const { root, chain } = flat;
+    if (root.type !== typescript_estree_1.AST_NODE_TYPES.Identifier)
+        return false;
+    if (chain.length !== 1 || !BRIDGE_SDK_IMPORTS.has(chain[0]))
+        return false;
+    return bindings.forgeBridgeNamespaces.has(root.name);
+};
+const isForgeReactUsePermissionsCall = (callee, bindings) => {
+    const flat = flattenMemberExpression(callee);
+    if (!flat)
+        return false;
+    const { root, chain } = flat;
+    if (root.type !== typescript_estree_1.AST_NODE_TYPES.Identifier)
+        return false;
+    if (chain.length !== 1 || chain[0] !== 'usePermissions')
+        return false;
+    return bindings.forgeReactNamespaces.has(root.name);
+};
+const doesAstUsePermissionsSdk = (ast) => {
+    const program = ast.parsed;
+    if (!program)
+        return false;
+    const bindings = collectImportBindings(program);
+    let found = false;
+    ast.traverse({
+        enter: (node) => {
+            if (found || node.type !== typescript_estree_1.AST_NODE_TYPES.CallExpression)
+                return;
+            const callee = node.callee;
+            if (callee.type === typescript_estree_1.AST_NODE_TYPES.Identifier && bindings.forgeBridgeSdk.has(callee.name)) {
+                found = true;
+                return;
+            }
+            if (callee.type === typescript_estree_1.AST_NODE_TYPES.Identifier && bindings.forgeReactUsePermissions.has(callee.name)) {
+                found = true;
+                return;
+            }
+            if (callee.type === typescript_estree_1.AST_NODE_TYPES.MemberExpression && isForgeApiPermissionsCall(callee, bindings)) {
+                found = true;
+                return;
+            }
+            if (callee.type === typescript_estree_1.AST_NODE_TYPES.MemberExpression && isForgeBridgeSdkCall(callee, bindings)) {
+                found = true;
+                return;
+            }
+            if (callee.type === typescript_estree_1.AST_NODE_TYPES.MemberExpression && isForgeReactUsePermissionsCall(callee, bindings)) {
+                found = true;
+            }
+        }
+    });
+    return found;
+};
+exports.doesAstUsePermissionsSdk = doesAstUsePermissionsSdk;