@forge/lint 5.1.1-next.5 → 5.1.1-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/out/lint/abstract-linter.js +1 -0
- package/out/lint/base-linter.js +5 -3
- package/out/lint/lint.js +1 -1
- package/out/lint/linter-interface.js +4 -2
- package/out/lint/linters/dynamic-properties-linter/dynamic-properties-permissions-linter.js +1 -0
- package/out/lint/linters/dynamic-properties-linter/verifiers/abstract-dynamic-properties-verifier.js +2 -3
- package/out/lint/linters/dynamic-properties-linter/verifiers/dynamic-properties-icon-verifier.js +2 -2
- package/out/lint/linters/dynamic-properties-linter/visitors/abstract-icon-visitor.js +19 -22
- package/out/lint/linters/dynamic-properties-linter/visitors/confluence-icon-visitor.js +5 -8
- package/out/lint/linters/dynamic-properties-linter/visitors/jira-icon-visitor.js +28 -31
- package/out/lint/linters/handler-linter/handler-linter.js +9 -6
- package/out/lint/linters/handler-linter/verifiers/handlers-verifier.js +1 -2
- package/out/lint/linters/handler-linter/visitors/exported-function-node-visitor.js +15 -14
- package/out/lint/linters/manifest-linter/abstract-manifest-linter.js +5 -5
- package/out/lint/linters/manifest-linter/deprecated-csp-permissions-manifest-linter.js +1 -2
- package/out/lint/linters/manifest-linter/permissions-manifest-linter.js +1 -2
- package/out/lint/linters/manifest-linter/remote-compute-manifest-linter.js +1 -2
- package/out/lint/linters/permission-linter/permission-linter.js +43 -22
- package/out/lint/linters/permission-linter/verifiers/content-property-verifier.js +1 -2
- package/out/lint/linters/permission-linter/verifiers/external-fetch-verifier.js +4 -4
- package/out/lint/linters/permission-linter/verifiers/image-url-verifier.js +2 -2
- package/out/lint/linters/permission-linter/verifiers/product-verifier.js +15 -12
- package/out/lint/linters/permission-linter/verifiers/storage-api-verifier.js +9 -4
- package/out/lint/linters/permission-linter/verifiers/ui-hook-verifier.js +1 -2
- package/out/lint/linters/permission-linter/visitors/external-fetch-call-visitor.js +11 -13
- package/out/lint/linters/permission-linter/visitors/image-url-visitor.js +2 -2
- package/out/lint/linters/permission-linter/visitors/product-node-visitor.js +3 -3
- package/out/lint/linters/permission-linter/visitors/storage-api-node-visitor.js +4 -1
- package/out/lint/linters/permission-linter/visitors/ui-hook-node-visitor.js +5 -1
- package/out/lint/linters/remote-linter/invoke-remote-linter.js +1 -0
- package/out/lint/linters/remote-linter/visitors/invoke-remote-call-visitor.js +18 -12
- package/out/lint/linters/verifier-interface.js +2 -0
- package/out/parse/parser.js +10 -2
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
package/out/lint/base-linter.js
CHANGED
|
@@ -4,13 +4,15 @@ const cli_shared_1 = require("@forge/cli-shared");
|
|
|
4
4
|
const linter_interface_1 = require("./linter-interface");
|
|
5
5
|
const abstract_linter_1 = require("./abstract-linter");
|
|
6
6
|
class BaseLinter extends abstract_linter_1.AbstractLinter {
|
|
7
|
+
environment;
|
|
8
|
+
logger;
|
|
9
|
+
nodeVisitors = [];
|
|
10
|
+
matches = new Map();
|
|
11
|
+
verifiers = {};
|
|
7
12
|
constructor(environment, logger) {
|
|
8
13
|
super(logger);
|
|
9
14
|
this.environment = environment;
|
|
10
15
|
this.logger = logger;
|
|
11
|
-
this.nodeVisitors = [];
|
|
12
|
-
this.matches = new Map();
|
|
13
|
-
this.verifiers = {};
|
|
14
16
|
}
|
|
15
17
|
getFixer() {
|
|
16
18
|
return undefined;
|
package/out/lint/lint.js
CHANGED
|
@@ -105,7 +105,7 @@ const lint = async (filesToLint, manifest, environment, logger, parseFunction =
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
return { ast: null, filepath: '' };
|
|
108
|
-
}))).filter((result) => !!
|
|
108
|
+
}))).filter((result) => !!result?.ast);
|
|
109
109
|
await Promise.all(linters.map((linter) => linter.bootstrap()));
|
|
110
110
|
const results = await Promise.all(linters.map((linter) => linter.batchExecute(lintInputs))).then(cli_shared_1.flat);
|
|
111
111
|
return results.map((result) => {
|
|
@@ -7,11 +7,13 @@ var LintClass;
|
|
|
7
7
|
LintClass["Warning"] = "warning";
|
|
8
8
|
})(LintClass = exports.LintClass || (exports.LintClass = {}));
|
|
9
9
|
class LintResult {
|
|
10
|
+
file;
|
|
11
|
+
fixer;
|
|
12
|
+
errors = [];
|
|
13
|
+
warnings = [];
|
|
10
14
|
constructor(file, fixer) {
|
|
11
15
|
this.file = file;
|
|
12
16
|
this.fixer = fixer;
|
|
13
|
-
this.errors = [];
|
|
14
|
-
this.warnings = [];
|
|
15
17
|
}
|
|
16
18
|
add(rule) {
|
|
17
19
|
switch (rule.class) {
|
|
@@ -9,6 +9,7 @@ const confluence_icon_visitor_1 = require("./visitors/confluence-icon-visitor");
|
|
|
9
9
|
const permission_linter_1 = require("../permission-linter/permission-linter");
|
|
10
10
|
const jira_icon_visitor_1 = require("./visitors/jira-icon-visitor");
|
|
11
11
|
class DynamicPropertiesPermissionsLinter extends base_linter_1.default {
|
|
12
|
+
manifest;
|
|
12
13
|
constructor(environment, manifest, logger) {
|
|
13
14
|
super(environment, logger);
|
|
14
15
|
this.manifest = manifest;
|
package/out/lint/linters/dynamic-properties-linter/verifiers/abstract-dynamic-properties-verifier.js
CHANGED
|
@@ -3,17 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AbstractDynamicPropertiesVerifier = void 0;
|
|
4
4
|
const verifier_interface_1 = require("../../verifier-interface");
|
|
5
5
|
class AbstractDynamicPropertiesVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
6
|
+
shouldRunCheck = false;
|
|
6
7
|
constructor(environment, manifest) {
|
|
7
8
|
super(environment, manifest);
|
|
8
|
-
this.shouldRunCheck = false;
|
|
9
9
|
Object.keys(this.manifest.modules || {}).forEach((moduleType) => {
|
|
10
|
-
var _a, _b;
|
|
11
10
|
switch (moduleType) {
|
|
12
11
|
case 'confluence:contentBylineItem':
|
|
13
12
|
case 'jira:issueGlance':
|
|
14
13
|
case 'jira:issueContext':
|
|
15
14
|
this.shouldRunCheck =
|
|
16
|
-
|
|
15
|
+
this.manifest.modules?.[moduleType]?.find((module) => module.dynamicProperties?.function) !==
|
|
17
16
|
undefined;
|
|
18
17
|
break;
|
|
19
18
|
}
|
package/out/lint/linters/dynamic-properties-linter/verifiers/dynamic-properties-icon-verifier.js
CHANGED
|
@@ -7,10 +7,10 @@ const utils_1 = require("../../utils");
|
|
|
7
7
|
const text_1 = require("../../../text");
|
|
8
8
|
const abstract_dynamic_properties_verifier_1 = require("./abstract-dynamic-properties-verifier");
|
|
9
9
|
class DynamicPropertiesIconVerifier extends abstract_dynamic_properties_verifier_1.AbstractDynamicPropertiesVerifier {
|
|
10
|
+
egressFilteringService;
|
|
10
11
|
constructor(environment, manifest) {
|
|
11
|
-
var _a, _b, _c, _d;
|
|
12
12
|
super(environment, manifest);
|
|
13
|
-
const allowList =
|
|
13
|
+
const allowList = this.manifest?.permissions?.external?.images ?? [];
|
|
14
14
|
const collectedUrls = allowList.filter((item) => typeof item === 'string');
|
|
15
15
|
this.egressFilteringService = new egress_1.EgressFilteringService(collectedUrls);
|
|
16
16
|
}
|
|
@@ -3,39 +3,36 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.AbstractIconVisitor = void 0;
|
|
4
4
|
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
|
5
5
|
class AbstractIconVisitor {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.isBundledUri = (iconUri) => iconUri.startsWith('resource:') || iconUri.startsWith('data:image');
|
|
8
|
-
this.handleIconNode = (iconNode, callback) => {
|
|
9
|
-
if ((iconNode === null || iconNode === void 0 ? void 0 : iconNode.type) === typescript_estree_1.AST_NODE_TYPES.Literal && typeof (iconNode === null || iconNode === void 0 ? void 0 : iconNode.value) === 'string') {
|
|
10
|
-
if (iconNode.value.trim() !== '' && !this.isBundledUri(iconNode.value.trim())) {
|
|
11
|
-
const iconUrl = this.transformArgsToIconUrl(iconNode);
|
|
12
|
-
callback(iconUrl);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
this.transformArgsToIconUrl = (iconNode) => ({
|
|
17
|
-
type: 'DYNAMIC_PROPERTIES_ICON',
|
|
18
|
-
url: iconNode.value,
|
|
19
|
-
line: iconNode.loc.start.line,
|
|
20
|
-
column: iconNode.loc.start.column
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
6
|
visit(node, parent, callback) {
|
|
24
|
-
var _a, _b;
|
|
25
7
|
switch (node.type) {
|
|
26
8
|
case typescript_estree_1.AST_NODE_TYPES.ArrowFunctionExpression:
|
|
27
|
-
if (
|
|
9
|
+
if (node.body?.type === typescript_estree_1.AST_NODE_TYPES.ObjectExpression) {
|
|
28
10
|
const iconDefinitionNode = this.getIconDefinitionNode(node.body);
|
|
29
|
-
this.handleIconNode(iconDefinitionNode
|
|
11
|
+
this.handleIconNode(iconDefinitionNode?.value, callback);
|
|
30
12
|
}
|
|
31
13
|
break;
|
|
32
14
|
case typescript_estree_1.AST_NODE_TYPES.ReturnStatement:
|
|
33
|
-
if (
|
|
15
|
+
if (node.argument?.type === typescript_estree_1.AST_NODE_TYPES.ObjectExpression) {
|
|
34
16
|
const iconDefinitionNode = this.getIconDefinitionNode(node.argument);
|
|
35
|
-
this.handleIconNode(iconDefinitionNode
|
|
17
|
+
this.handleIconNode(iconDefinitionNode?.value, callback);
|
|
36
18
|
}
|
|
37
19
|
break;
|
|
38
20
|
}
|
|
39
21
|
}
|
|
22
|
+
isBundledUri = (iconUri) => iconUri.startsWith('resource:') || iconUri.startsWith('data:image');
|
|
23
|
+
handleIconNode = (iconNode, callback) => {
|
|
24
|
+
if (iconNode?.type === typescript_estree_1.AST_NODE_TYPES.Literal && typeof iconNode?.value === 'string') {
|
|
25
|
+
if (iconNode.value.trim() !== '' && !this.isBundledUri(iconNode.value.trim())) {
|
|
26
|
+
const iconUrl = this.transformArgsToIconUrl(iconNode);
|
|
27
|
+
callback(iconUrl);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
transformArgsToIconUrl = (iconNode) => ({
|
|
32
|
+
type: 'DYNAMIC_PROPERTIES_ICON',
|
|
33
|
+
url: iconNode.value,
|
|
34
|
+
line: iconNode.loc.start.line,
|
|
35
|
+
column: iconNode.loc.start.column
|
|
36
|
+
});
|
|
40
37
|
}
|
|
41
38
|
exports.AbstractIconVisitor = AbstractIconVisitor;
|
|
@@ -4,16 +4,13 @@ exports.ConfluenceIconVisitor = void 0;
|
|
|
4
4
|
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
|
5
5
|
const abstract_icon_visitor_1 = require("./abstract-icon-visitor");
|
|
6
6
|
class ConfluenceIconVisitor extends abstract_icon_visitor_1.AbstractIconVisitor {
|
|
7
|
-
constructor() {
|
|
8
|
-
super(...arguments);
|
|
9
|
-
this.isIconPropertyDefinition = (node) => {
|
|
10
|
-
return (node.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
11
|
-
((node.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && node.key.name === 'icon') ||
|
|
12
|
-
(node.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && node.key.value === 'icon')));
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
7
|
getIconDefinitionNode(node) {
|
|
16
8
|
return node.properties.find(this.isIconPropertyDefinition);
|
|
17
9
|
}
|
|
10
|
+
isIconPropertyDefinition = (node) => {
|
|
11
|
+
return (node.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
12
|
+
((node.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && node.key.name === 'icon') ||
|
|
13
|
+
(node.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && node.key.value === 'icon')));
|
|
14
|
+
};
|
|
18
15
|
}
|
|
19
16
|
exports.ConfluenceIconVisitor = ConfluenceIconVisitor;
|
|
@@ -4,40 +4,37 @@ exports.JiraIconVisitor = void 0;
|
|
|
4
4
|
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
|
5
5
|
const abstract_icon_visitor_1 = require("./abstract-icon-visitor");
|
|
6
6
|
class JiraIconVisitor extends abstract_icon_visitor_1.AbstractIconVisitor {
|
|
7
|
-
constructor() {
|
|
8
|
-
super(...arguments);
|
|
9
|
-
this.isStatusPropertyDefinition = (node) => {
|
|
10
|
-
return (node.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
11
|
-
((node.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && node.key.value === 'status') ||
|
|
12
|
-
(node.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && node.key.name === 'status')) &&
|
|
13
|
-
node.value.type === typescript_estree_1.AST_NODE_TYPES.ObjectExpression &&
|
|
14
|
-
node.value.properties.find((prop) => {
|
|
15
|
-
return (prop.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
16
|
-
((prop.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.key.value === 'type') ||
|
|
17
|
-
(prop.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.key.name === 'type')) &&
|
|
18
|
-
((prop.value.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.value.value === 'icon') ||
|
|
19
|
-
(prop.value.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.value.name === 'icon')));
|
|
20
|
-
}) !== undefined);
|
|
21
|
-
};
|
|
22
|
-
this.extractIconDefinition = (node) => {
|
|
23
|
-
if (!node) {
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
const valueDefinitionNode = node === null || node === void 0 ? void 0 : node.value.properties.find((prop) => {
|
|
27
|
-
return (prop.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
28
|
-
((prop.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.key.value === 'value') ||
|
|
29
|
-
(prop.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.key.name === 'value')));
|
|
30
|
-
});
|
|
31
|
-
return valueDefinitionNode.value.properties.find((prop) => {
|
|
32
|
-
return (prop.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
33
|
-
((prop.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.key.value === 'url') ||
|
|
34
|
-
(prop.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.key.name === 'url')));
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
7
|
getIconDefinitionNode(node) {
|
|
39
8
|
const statusPropertyNode = node.properties.find(this.isStatusPropertyDefinition);
|
|
40
9
|
return this.extractIconDefinition(statusPropertyNode);
|
|
41
10
|
}
|
|
11
|
+
isStatusPropertyDefinition = (node) => {
|
|
12
|
+
return (node.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
13
|
+
((node.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && node.key.value === 'status') ||
|
|
14
|
+
(node.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && node.key.name === 'status')) &&
|
|
15
|
+
node.value.type === typescript_estree_1.AST_NODE_TYPES.ObjectExpression &&
|
|
16
|
+
node.value.properties.find((prop) => {
|
|
17
|
+
return (prop.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
18
|
+
((prop.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.key.value === 'type') ||
|
|
19
|
+
(prop.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.key.name === 'type')) &&
|
|
20
|
+
((prop.value.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.value.value === 'icon') ||
|
|
21
|
+
(prop.value.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.value.name === 'icon')));
|
|
22
|
+
}) !== undefined);
|
|
23
|
+
};
|
|
24
|
+
extractIconDefinition = (node) => {
|
|
25
|
+
if (!node) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const valueDefinitionNode = node?.value.properties.find((prop) => {
|
|
29
|
+
return (prop.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
30
|
+
((prop.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.key.value === 'value') ||
|
|
31
|
+
(prop.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.key.name === 'value')));
|
|
32
|
+
});
|
|
33
|
+
return valueDefinitionNode.value.properties.find((prop) => {
|
|
34
|
+
return (prop.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
35
|
+
((prop.key.type === typescript_estree_1.AST_NODE_TYPES.Literal && prop.key.value === 'url') ||
|
|
36
|
+
(prop.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier && prop.key.name === 'url')));
|
|
37
|
+
});
|
|
38
|
+
};
|
|
42
39
|
}
|
|
43
40
|
exports.JiraIconVisitor = JiraIconVisitor;
|
|
@@ -7,10 +7,10 @@ const base_linter_1 = tslib_1.__importDefault(require("../../base-linter"));
|
|
|
7
7
|
const handlers_verifier_1 = require("./verifiers/handlers-verifier");
|
|
8
8
|
const visitors_1 = require("./visitors");
|
|
9
9
|
class HandlerLinter extends base_linter_1.default {
|
|
10
|
+
manifest;
|
|
10
11
|
constructor(environment, manifest, logger) {
|
|
11
12
|
super(environment, logger);
|
|
12
13
|
this.manifest = manifest;
|
|
13
|
-
this.getHandlerFileRegex = (fileName) => new RegExp(`^src(\/|\\\\)${fileName}\.[tj](s|sx)$`);
|
|
14
14
|
}
|
|
15
15
|
async bootstrap() {
|
|
16
16
|
this.nodeVisitors = [new visitors_1.ExportedFunctionNodeVisitor()];
|
|
@@ -19,20 +19,23 @@ class HandlerLinter extends base_linter_1.default {
|
|
|
19
19
|
functionHandlers: new handlers_verifier_1.HandlersVerifier(humanReadableEnvironment, this.manifest)
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
+
getHandlerFileRegex = (fileName) => new RegExp(`^src(\/|\\\\)${fileName}\.[tj](s|sx)$`);
|
|
22
23
|
getFunctionHandlers(manifest) {
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
return manifest.modules?.function
|
|
25
|
+
?.filter((funcs) => funcs.handler && funcs.handler.split('.').length === 2)
|
|
26
|
+
?.map((funcs) => {
|
|
25
27
|
const [file, method] = funcs.handler.split('.');
|
|
26
28
|
return {
|
|
27
29
|
file,
|
|
28
30
|
method,
|
|
29
31
|
key: funcs.key
|
|
30
32
|
};
|
|
31
|
-
})
|
|
33
|
+
})
|
|
34
|
+
.filter((handler) => handler.method && handler.file);
|
|
32
35
|
}
|
|
33
36
|
setupMatchesMap(filepath) {
|
|
34
37
|
const handlers = this.getFunctionHandlers(this.manifest);
|
|
35
|
-
const filteredHandlers = handlers
|
|
38
|
+
const filteredHandlers = handlers?.filter((handler) => this.getHandlerFileRegex(handler.file).test(filepath));
|
|
36
39
|
this.matches.set(filepath, {
|
|
37
40
|
functionHandlers: {
|
|
38
41
|
expectedMethods: filteredHandlers || [],
|
|
@@ -42,7 +45,7 @@ class HandlerLinter extends base_linter_1.default {
|
|
|
42
45
|
}
|
|
43
46
|
filterLintInput(inputs) {
|
|
44
47
|
const handlers = this.getFunctionHandlers(this.manifest);
|
|
45
|
-
return inputs.filter((input) => handlers
|
|
48
|
+
return inputs.filter((input) => handlers?.find((handler) => this.getHandlerFileRegex(handler.file).test(input.filepath)));
|
|
46
49
|
}
|
|
47
50
|
addLintCriteriaMatch(exportedMethod, filepath) {
|
|
48
51
|
const criteriaMatches = this.matches.get(filepath);
|
|
@@ -9,8 +9,7 @@ class HandlersVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
|
9
9
|
return linter_interface_1.LintClass.Warning;
|
|
10
10
|
}
|
|
11
11
|
async process(input) {
|
|
12
|
-
|
|
13
|
-
if (!((_b = (_a = this.manifest) === null || _a === void 0 ? void 0 : _a.modules) === null || _b === void 0 ? void 0 : _b.function)) {
|
|
12
|
+
if (!this.manifest?.modules?.function) {
|
|
14
13
|
return [];
|
|
15
14
|
}
|
|
16
15
|
const missingMethods = input.expectedMethods.filter((func) => !input.exportedMethods.find((exportedMethod) => exportedMethod.method === func.method));
|
|
@@ -23,21 +23,20 @@ class ExportedFunctionNodeVisitor {
|
|
|
23
23
|
}
|
|
24
24
|
visitSpecifiedNamedExports(node, specifier, callback) {
|
|
25
25
|
if (specifier.type === typescript_estree_1.AST_NODE_TYPES.ExportSpecifier && specifier.local.type === typescript_estree_1.AST_NODE_TYPES.Identifier) {
|
|
26
|
-
callback(this.createExportedMethod(specifier.exported.name,
|
|
26
|
+
callback(this.createExportedMethod(specifier.exported.name, { ...node.loc.start }));
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
visitInlineNamedExport(node, callback) {
|
|
30
|
-
|
|
31
|
-
if (((_a = node.declaration) === null || _a === void 0 ? void 0 : _a.type) === typescript_estree_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
30
|
+
if (node.declaration?.type === typescript_estree_1.AST_NODE_TYPES.VariableDeclaration) {
|
|
32
31
|
node.declaration.declarations.forEach((declarator) => {
|
|
33
32
|
if (declarator.id && declarator.id.type === typescript_estree_1.AST_NODE_TYPES.Identifier) {
|
|
34
|
-
callback(this.createExportedMethod(declarator.id.name,
|
|
33
|
+
callback(this.createExportedMethod(declarator.id.name, { ...declarator.id.loc.start }));
|
|
35
34
|
}
|
|
36
35
|
});
|
|
37
36
|
}
|
|
38
|
-
else if (
|
|
39
|
-
if (
|
|
40
|
-
callback(this.createExportedMethod(node.declaration.id.name,
|
|
37
|
+
else if (node.declaration?.type === typescript_estree_1.AST_NODE_TYPES.FunctionDeclaration) {
|
|
38
|
+
if (node.declaration?.id && node.declaration?.id.type === typescript_estree_1.AST_NODE_TYPES.Identifier) {
|
|
39
|
+
callback(this.createExportedMethod(node.declaration.id.name, { ...node.declaration.id.loc.start }));
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
}
|
|
@@ -48,21 +47,23 @@ class ExportedFunctionNodeVisitor {
|
|
|
48
47
|
}
|
|
49
48
|
visitMemberExpression(node, callback) {
|
|
50
49
|
if (node.property.type === typescript_estree_1.AST_NODE_TYPES.Identifier && this.isExports(node.object)) {
|
|
51
|
-
callback(this.createExportedMethod(node.property.name,
|
|
50
|
+
callback(this.createExportedMethod(node.property.name, { ...node.property.loc.start }));
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
tryAddObjectExpressionPropertiesExportedMethods(node, callback) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
(_b = node.right.properties) === null || _b === void 0 ? void 0 : _b.forEach((property) => {
|
|
54
|
+
if (node.right?.type === typescript_estree_1.AST_NODE_TYPES.ObjectExpression) {
|
|
55
|
+
node.right.properties?.forEach((property) => {
|
|
58
56
|
if (property.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier) {
|
|
59
|
-
callback(this.createExportedMethod(property.key.name,
|
|
57
|
+
callback(this.createExportedMethod(property.key.name, { ...property.key.loc.start }));
|
|
60
58
|
}
|
|
61
59
|
});
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
createExportedMethod(method, location) {
|
|
65
|
-
return
|
|
63
|
+
return {
|
|
64
|
+
method,
|
|
65
|
+
...location
|
|
66
|
+
};
|
|
66
67
|
}
|
|
67
68
|
isExports(node) {
|
|
68
69
|
return ((node.type === typescript_estree_1.AST_NODE_TYPES.MemberExpression &&
|
|
@@ -70,7 +71,7 @@ class ExportedFunctionNodeVisitor {
|
|
|
70
71
|
node.object.name === 'module' &&
|
|
71
72
|
node.property.type === typescript_estree_1.AST_NODE_TYPES.Identifier &&
|
|
72
73
|
node.property.name === 'exports') ||
|
|
73
|
-
(
|
|
74
|
+
(node?.type === typescript_estree_1.AST_NODE_TYPES.Identifier && node?.name === 'exports'));
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
exports.ExportedFunctionNodeVisitor = ExportedFunctionNodeVisitor;
|
|
@@ -5,14 +5,15 @@ const manifest_1 = require("@forge/manifest");
|
|
|
5
5
|
const linter_interface_1 = require("../../linter-interface");
|
|
6
6
|
const abstract_linter_1 = require("../../abstract-linter");
|
|
7
7
|
class AbstractManifestLinter extends abstract_linter_1.AbstractLinter {
|
|
8
|
+
type;
|
|
9
|
+
processor;
|
|
8
10
|
constructor(type, logger) {
|
|
9
11
|
super(logger);
|
|
10
12
|
this.type = type;
|
|
11
13
|
}
|
|
12
14
|
mapManifestResponse(results, fixer) {
|
|
13
|
-
var _a;
|
|
14
15
|
const manifestLintResults = new linter_interface_1.LintResult(manifest_1.MANIFEST_FILE, fixer);
|
|
15
|
-
manifestLintResults.batchAdd(...(
|
|
16
|
+
manifestLintResults.batchAdd(...(this.getResultsForFixer(results).map(({ level, message, line, column, reference, metadata }) => {
|
|
16
17
|
return {
|
|
17
18
|
class: level,
|
|
18
19
|
message,
|
|
@@ -21,7 +22,7 @@ class AbstractManifestLinter extends abstract_linter_1.AbstractLinter {
|
|
|
21
22
|
column,
|
|
22
23
|
metadata
|
|
23
24
|
};
|
|
24
|
-
})
|
|
25
|
+
}) ?? []).sort((a, b) => a.line - b.line));
|
|
25
26
|
return manifestLintResults;
|
|
26
27
|
}
|
|
27
28
|
getProcessor() {
|
|
@@ -42,8 +43,7 @@ class AbstractManifestLinter extends abstract_linter_1.AbstractLinter {
|
|
|
42
43
|
return undefined;
|
|
43
44
|
}
|
|
44
45
|
getResultsForFixer(results) {
|
|
45
|
-
|
|
46
|
-
return ((_a = results.errors) === null || _a === void 0 ? void 0 : _a.filter((error) => !error.metadata)) || [];
|
|
46
|
+
return results.errors?.filter((error) => !error.metadata) || [];
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
exports.AbstractManifestLinter = AbstractManifestLinter;
|
|
@@ -12,8 +12,7 @@ class DeprecatedCspPermissionsManifestLinter extends abstract_manifest_linter_1.
|
|
|
12
12
|
return permission_linter_1.fixMissingPermissions;
|
|
13
13
|
}
|
|
14
14
|
getResultsForFixer(results) {
|
|
15
|
-
|
|
16
|
-
return ((_a = results.errors) === null || _a === void 0 ? void 0 : _a.filter((error) => { var _a; return (_a = error.metadata) === null || _a === void 0 ? void 0 : _a.missingContentStylePermission; })) || [];
|
|
15
|
+
return results.errors?.filter((error) => error.metadata?.missingContentStylePermission) || [];
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
exports.DeprecatedCspPermissionsManifestLinter = DeprecatedCspPermissionsManifestLinter;
|
|
@@ -12,8 +12,7 @@ class PermissionsManifestLinter extends abstract_manifest_linter_1.AbstractManif
|
|
|
12
12
|
return permission_linter_1.fixMissingPermissions;
|
|
13
13
|
}
|
|
14
14
|
getResultsForFixer(results) {
|
|
15
|
-
|
|
16
|
-
return ((_a = results.errors) === null || _a === void 0 ? void 0 : _a.filter((error) => { var _a; return (_a = error.metadata) === null || _a === void 0 ? void 0 : _a.missingPermission; })) || [];
|
|
15
|
+
return results.errors?.filter((error) => error.metadata?.missingPermission) || [];
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
exports.PermissionsManifestLinter = PermissionsManifestLinter;
|
|
@@ -12,8 +12,7 @@ class RemoteComputeManifestLinter extends abstract_manifest_linter_1.AbstractMan
|
|
|
12
12
|
return permission_linter_1.fixMissingPermissions;
|
|
13
13
|
}
|
|
14
14
|
getResultsForFixer(results) {
|
|
15
|
-
|
|
16
|
-
return ((_a = results.errors) === null || _a === void 0 ? void 0 : _a.filter((error) => { var _a; return (_a = error.metadata) === null || _a === void 0 ? void 0 : _a.missingPermission; })) || [];
|
|
15
|
+
return results.errors?.filter((error) => error.metadata?.missingPermission) || [];
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
exports.RemoteComputeManifestLinter = RemoteComputeManifestLinter;
|
|
@@ -32,47 +32,70 @@ const findMissingPermissions = (errors, warnings, permissionsType, state) => {
|
|
|
32
32
|
};
|
|
33
33
|
exports.findMissingPermissions = findMissingPermissions;
|
|
34
34
|
const fixMissingPermissions = async (errors, warnings, state) => {
|
|
35
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
36
35
|
const missingScopes = (0, exports.findMissingPermissions)(errors, warnings, 'missingPermission', state);
|
|
37
36
|
const missingExternalFetchBackend = (0, exports.findMissingPermissions)(errors, warnings, 'missingExternalFetchPermission', state);
|
|
38
37
|
const missingContentStyle = (0, exports.findMissingPermissions)(errors, warnings, 'missingContentStylePermission', state);
|
|
39
38
|
const missingExternalImage = (0, exports.findMissingPermissions)(errors, warnings, 'missingExternalImagesPermission', state);
|
|
40
|
-
const currentPermissions = (
|
|
41
|
-
const currentRemotes = (
|
|
42
|
-
const currentScopes =
|
|
39
|
+
const currentPermissions = (await state.configFile.readConfig())?.permissions ?? {};
|
|
40
|
+
const currentRemotes = (await state.configFile.readConfig())?.remotes ?? [];
|
|
41
|
+
const currentScopes = currentPermissions?.scopes ?? [];
|
|
43
42
|
currentScopes.forEach((e) => missingScopes.add(e));
|
|
44
|
-
const currentExternalFetchBackend =
|
|
43
|
+
const currentExternalFetchBackend = currentPermissions?.external?.fetch?.backend ?? [];
|
|
45
44
|
currentExternalFetchBackend
|
|
46
45
|
.map((item) => {
|
|
47
|
-
var _a;
|
|
48
46
|
if (typeof item === 'string')
|
|
49
47
|
return item;
|
|
50
|
-
return
|
|
48
|
+
return currentRemotes.find((remote) => remote.key == item.remote)?.baseUrl;
|
|
51
49
|
})
|
|
52
50
|
.filter((item) => typeof item === 'string')
|
|
53
51
|
.forEach((e) => missingExternalFetchBackend.add(e));
|
|
54
52
|
const arrayMissingExternalFetchBackend = [...missingExternalFetchBackend];
|
|
55
|
-
const currentContentStyles =
|
|
53
|
+
const currentContentStyles = currentPermissions?.content?.styles ?? [];
|
|
56
54
|
currentContentStyles.forEach((e) => missingContentStyle.add(e));
|
|
57
55
|
const arrayMissingContentStyles = [...missingContentStyle];
|
|
58
|
-
const currentExternalImages =
|
|
56
|
+
const currentExternalImages = currentPermissions?.external?.images ?? [];
|
|
59
57
|
currentExternalImages.forEach((e) => missingExternalImage.add(e));
|
|
60
58
|
const arrayMissingExternalImages = [...missingExternalImage];
|
|
61
|
-
const config =
|
|
59
|
+
const config = {
|
|
60
|
+
...currentPermissions,
|
|
61
|
+
scopes: [...missingScopes]
|
|
62
|
+
};
|
|
62
63
|
if (arrayMissingExternalFetchBackend.length > 0) {
|
|
63
|
-
config.external =
|
|
64
|
+
config.external = {
|
|
65
|
+
...config.external,
|
|
66
|
+
fetch: {
|
|
67
|
+
...config.external?.fetch,
|
|
68
|
+
backend: arrayMissingExternalFetchBackend
|
|
69
|
+
}
|
|
70
|
+
};
|
|
64
71
|
}
|
|
65
72
|
if (arrayMissingExternalImages.length > 0) {
|
|
66
|
-
config.external =
|
|
73
|
+
config.external = {
|
|
74
|
+
...config.external,
|
|
75
|
+
images: arrayMissingExternalImages
|
|
76
|
+
};
|
|
67
77
|
}
|
|
68
78
|
if (arrayMissingContentStyles.length > 0) {
|
|
69
|
-
config.content =
|
|
79
|
+
config.content = {
|
|
80
|
+
...config.content,
|
|
81
|
+
styles: arrayMissingContentStyles
|
|
82
|
+
};
|
|
70
83
|
}
|
|
71
84
|
await state.configFile.writeToConfigFile('permissions', config);
|
|
72
85
|
return state;
|
|
73
86
|
};
|
|
74
87
|
exports.fixMissingPermissions = fixMissingPermissions;
|
|
75
88
|
class PermissionLinter extends base_linter_1.default {
|
|
89
|
+
manifest;
|
|
90
|
+
static JIRA_CACHE_KEY = 'permissions-jira-cache-key';
|
|
91
|
+
static JSM_CACHE_KEY = 'permissions-jsm-cache-key';
|
|
92
|
+
static JSW_CACHE_KEY = 'permissions-jsw-cache-key';
|
|
93
|
+
static CONFLUENCE_CACHE_KEY = 'permissions-confluence-cache-key';
|
|
94
|
+
static BITBUCKET_CACHE_KEY = 'permissions-bitbucket-cache-key';
|
|
95
|
+
jira;
|
|
96
|
+
confluence;
|
|
97
|
+
bitbucket;
|
|
98
|
+
cache;
|
|
76
99
|
constructor(environment, manifest, logger) {
|
|
77
100
|
super(environment, logger);
|
|
78
101
|
this.manifest = manifest;
|
|
@@ -110,7 +133,11 @@ class PermissionLinter extends base_linter_1.default {
|
|
|
110
133
|
];
|
|
111
134
|
}
|
|
112
135
|
const [jiraSwagger, jsmSwagger, jswSwagger, confluenceSwagger, bitbucketSwagger] = await Promise.all(swaggerFiles);
|
|
113
|
-
this.jira = this.processPaths(
|
|
136
|
+
this.jira = this.processPaths({
|
|
137
|
+
...jiraSwagger.paths,
|
|
138
|
+
...jsmSwagger.paths,
|
|
139
|
+
...jswSwagger.paths
|
|
140
|
+
});
|
|
114
141
|
this.confluence = this.processPaths(confluenceSwagger.paths);
|
|
115
142
|
this.bitbucket = this.processPaths(bitbucketSwagger.paths);
|
|
116
143
|
this.nodeVisitors = [
|
|
@@ -174,12 +201,11 @@ class PermissionLinter extends base_linter_1.default {
|
|
|
174
201
|
}
|
|
175
202
|
}
|
|
176
203
|
async getProductPaths(cacheKey, url) {
|
|
177
|
-
|
|
178
|
-
const cached = (_a = this.cache) === null || _a === void 0 ? void 0 : _a.get(cacheKey);
|
|
204
|
+
const cached = this.cache?.get(cacheKey);
|
|
179
205
|
if (!cached) {
|
|
180
206
|
const response = await (0, node_fetch_1.default)(url);
|
|
181
207
|
const jsonBody = await response.json();
|
|
182
|
-
void
|
|
208
|
+
void this.cache?.set(cacheKey, jsonBody, 12 * ONE_HOUR_MS);
|
|
183
209
|
return jsonBody;
|
|
184
210
|
}
|
|
185
211
|
return cached;
|
|
@@ -198,8 +224,3 @@ class PermissionLinter extends base_linter_1.default {
|
|
|
198
224
|
}
|
|
199
225
|
}
|
|
200
226
|
exports.PermissionLinter = PermissionLinter;
|
|
201
|
-
PermissionLinter.JIRA_CACHE_KEY = 'permissions-jira-cache-key';
|
|
202
|
-
PermissionLinter.JSM_CACHE_KEY = 'permissions-jsm-cache-key';
|
|
203
|
-
PermissionLinter.JSW_CACHE_KEY = 'permissions-jsw-cache-key';
|
|
204
|
-
PermissionLinter.CONFLUENCE_CACHE_KEY = 'permissions-confluence-cache-key';
|
|
205
|
-
PermissionLinter.BITBUCKET_CACHE_KEY = 'permissions-bitbucket-cache-key';
|
|
@@ -88,8 +88,7 @@ class ContentPropertyVerifier extends verifier_interface_1.BaseLintIssueVerifier
|
|
|
88
88
|
return lintErrors;
|
|
89
89
|
}
|
|
90
90
|
getMissingScopes({ context, method }) {
|
|
91
|
-
|
|
92
|
-
const existingScopes = (_b = (_a = this.manifest) === null || _a === void 0 ? void 0 : _a.permissions) === null || _b === void 0 ? void 0 : _b.scopes;
|
|
91
|
+
const existingScopes = this.manifest?.permissions?.scopes;
|
|
93
92
|
return (0, manifest_1.getMissingScopes)(existingScopes, exports.CONTENT_PROPERTY_PERMISSIONS_MAP[context][method]);
|
|
94
93
|
}
|
|
95
94
|
}
|
|
@@ -7,14 +7,14 @@ const verifier_interface_1 = require("../../verifier-interface");
|
|
|
7
7
|
const text_1 = require("../../../text");
|
|
8
8
|
const utils_1 = require("../../utils");
|
|
9
9
|
class ExternalFetchVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
10
|
+
egressFilteringService;
|
|
10
11
|
constructor(environment, manifest) {
|
|
11
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
12
12
|
super(environment, manifest);
|
|
13
|
-
const allowList =
|
|
14
|
-
const remoteList =
|
|
13
|
+
const allowList = this.manifest?.permissions?.external?.fetch?.backend ?? [];
|
|
14
|
+
const remoteList = this.manifest?.remotes ?? [];
|
|
15
15
|
const collectedUrls = allowList.filter((item) => typeof item === 'string');
|
|
16
16
|
const collectedRemotes = allowList
|
|
17
|
-
.map((item) =>
|
|
17
|
+
.map((item) => typeof item !== 'string' && remoteList.find((remoteItem) => remoteItem.key === item.remote)?.baseUrl)
|
|
18
18
|
.filter((x) => typeof x === 'string');
|
|
19
19
|
this.egressFilteringService = new egress_1.EgressFilteringService(collectedUrls.concat(collectedRemotes));
|
|
20
20
|
}
|
|
@@ -7,10 +7,10 @@ const verifier_interface_1 = require("../../verifier-interface");
|
|
|
7
7
|
const text_1 = require("../../../text");
|
|
8
8
|
const utils_1 = require("../../utils");
|
|
9
9
|
class ImageUrlVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
10
|
+
egressFilteringService;
|
|
10
11
|
constructor(environment, manifest) {
|
|
11
|
-
var _a, _b, _c, _d;
|
|
12
12
|
super(environment, manifest);
|
|
13
|
-
const allowList =
|
|
13
|
+
const allowList = this.manifest?.permissions?.external?.images ?? [];
|
|
14
14
|
const collectedUrls = allowList.filter((item) => typeof item === 'string');
|
|
15
15
|
this.egressFilteringService = new egress_1.EgressFilteringService(collectedUrls);
|
|
16
16
|
}
|
|
@@ -9,6 +9,8 @@ const manifest_1 = require("@forge/manifest");
|
|
|
9
9
|
const text_1 = require("../../../text");
|
|
10
10
|
const array_prototype_flatmap_1 = tslib_1.__importDefault(require("array.prototype.flatmap"));
|
|
11
11
|
class ProductVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
12
|
+
pathMap;
|
|
13
|
+
product;
|
|
12
14
|
constructor(environment, manifest, pathMap, product) {
|
|
13
15
|
super(environment, manifest);
|
|
14
16
|
this.pathMap = pathMap;
|
|
@@ -18,7 +20,6 @@ class ProductVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
|
18
20
|
return linter_interface_1.LintClass.Error;
|
|
19
21
|
}
|
|
20
22
|
async process(apiCalls) {
|
|
21
|
-
var _a;
|
|
22
23
|
const rules = [];
|
|
23
24
|
const lintCriteriaMatches = new Set();
|
|
24
25
|
for (const apiCall of apiCalls) {
|
|
@@ -28,14 +29,14 @@ class ProductVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
for (const apiCall of lintCriteriaMatches) {
|
|
31
|
-
if (!
|
|
32
|
+
if (!apiCall?.regex) {
|
|
32
33
|
continue;
|
|
33
34
|
}
|
|
34
35
|
const missingScopes = await this.getMissingPermissionScopes(apiCall);
|
|
35
36
|
for (const permission of missingScopes) {
|
|
36
37
|
rules.push({
|
|
37
38
|
class: this.getLintClass(),
|
|
38
|
-
message: text_1.messages.verifiers.product.message((0, cli_shared_1.capitalise)(this.product), apiCall.method.toUpperCase(),
|
|
39
|
+
message: text_1.messages.verifiers.product.message((0, cli_shared_1.capitalise)(this.product), apiCall.method.toUpperCase(), this.pathMap.get(apiCall.regex)?.originalPath, permission),
|
|
39
40
|
reference: text_1.messages.verifiers.product.reference,
|
|
40
41
|
line: apiCall.line,
|
|
41
42
|
column: apiCall.column,
|
|
@@ -51,34 +52,36 @@ class ProductVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
|
51
52
|
const pathWithoutQuery = apiCall.path.replace(/\?.+$/, '');
|
|
52
53
|
for (const regex of this.pathMap.keys()) {
|
|
53
54
|
if (regex.test(pathWithoutQuery)) {
|
|
54
|
-
return
|
|
55
|
+
return {
|
|
56
|
+
...apiCall,
|
|
57
|
+
regex,
|
|
58
|
+
path: pathWithoutQuery,
|
|
59
|
+
method: apiCall.method.toLowerCase()
|
|
60
|
+
};
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
64
|
getMissingPermissionScopes(apiCall) {
|
|
59
|
-
var _a, _b;
|
|
60
65
|
const current = this.getRequiredScopes(apiCall);
|
|
61
66
|
const beta = this.getBetaScopes(apiCall);
|
|
62
|
-
return (0, manifest_1.getMissingScopes)(
|
|
67
|
+
return (0, manifest_1.getMissingScopes)(this.manifest?.permissions?.scopes, { current, beta });
|
|
63
68
|
}
|
|
64
69
|
getRequiredScopes({ regex, method }) {
|
|
65
|
-
var _a, _b, _c, _d;
|
|
66
70
|
if (!regex)
|
|
67
71
|
return [];
|
|
68
|
-
const oAuth2Scopes =
|
|
72
|
+
const oAuth2Scopes = this.pathMap.get(regex)?.methods[method]?.['x-atlassian-oauth2-scopes'];
|
|
69
73
|
if (oAuth2Scopes) {
|
|
70
74
|
return (0, array_prototype_flatmap_1.default)(oAuth2Scopes.filter((scope) => scope.state === 'Current').map((scope) => scope.scopes), (x) => x);
|
|
71
75
|
}
|
|
72
|
-
const swaggerEntries =
|
|
76
|
+
const swaggerEntries = this.pathMap.get(regex)?.methods[method]?.security;
|
|
73
77
|
if (!swaggerEntries)
|
|
74
78
|
return [];
|
|
75
|
-
return swaggerEntries
|
|
79
|
+
return swaggerEntries?.reduce((prev, curr) => [...prev, ...(curr['OAuth2'] || []), ...(curr['oAuthDefinitions'] || [])], []);
|
|
76
80
|
}
|
|
77
81
|
getBetaScopes({ regex, method }) {
|
|
78
|
-
var _a, _b;
|
|
79
82
|
if (!regex)
|
|
80
83
|
return [];
|
|
81
|
-
const oAuth2Scopes =
|
|
84
|
+
const oAuth2Scopes = this.pathMap.get(regex)?.methods[method]?.['x-atlassian-oauth2-scopes'];
|
|
82
85
|
if (!oAuth2Scopes)
|
|
83
86
|
return [];
|
|
84
87
|
return (0, array_prototype_flatmap_1.default)(oAuth2Scopes.filter((scope) => scope.state === 'Beta').map((scope) => scope.scopes), (x) => x);
|
|
@@ -10,13 +10,18 @@ class StorageAPIVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
|
10
10
|
return linter_interface_1.LintClass.Error;
|
|
11
11
|
}
|
|
12
12
|
async process(apiCalls) {
|
|
13
|
-
|
|
14
|
-
if ((_b = (_a = this.manifest.permissions) === null || _a === void 0 ? void 0 : _a.scopes) === null || _b === void 0 ? void 0 : _b.includes(STORAGE_PERMISSION)) {
|
|
13
|
+
if (this.manifest.permissions?.scopes?.includes(STORAGE_PERMISSION)) {
|
|
15
14
|
return [];
|
|
16
15
|
}
|
|
17
|
-
return apiCalls.map((apiCall) => (
|
|
16
|
+
return apiCalls.map((apiCall) => ({
|
|
17
|
+
class: this.getLintClass(),
|
|
18
|
+
message: text_1.messages.verifiers.storage.message(STORAGE_PERMISSION),
|
|
19
|
+
reference: text_1.messages.verifiers.storage.reference,
|
|
20
|
+
...apiCall,
|
|
21
|
+
metadata: {
|
|
18
22
|
missingPermission: STORAGE_PERMISSION
|
|
19
|
-
}
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
exports.StorageAPIVerifier = StorageAPIVerifier;
|
|
@@ -24,13 +24,12 @@ class UIHookVerifier extends verifier_interface_1.BaseLintIssueVerifier {
|
|
|
24
24
|
return linter_interface_1.LintClass.Error;
|
|
25
25
|
}
|
|
26
26
|
async process(apiCalls) {
|
|
27
|
-
var _a;
|
|
28
27
|
const rules = [];
|
|
29
28
|
for (const apiCall of apiCalls) {
|
|
30
29
|
const requiredPermissions = FORGE_UI_HOOK_PERMISSIONS[apiCall.hookName];
|
|
31
30
|
if (!requiredPermissions)
|
|
32
31
|
return rules;
|
|
33
|
-
const missingPermissions = await (0, manifest_1.getMissingScopes)(
|
|
32
|
+
const missingPermissions = await (0, manifest_1.getMissingScopes)(this.manifest.permissions?.scopes, requiredPermissions);
|
|
34
33
|
missingPermissions.forEach((perm) => rules.push(this.getHookRule(apiCall, perm)));
|
|
35
34
|
}
|
|
36
35
|
return rules;
|
|
@@ -5,20 +5,12 @@ const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
|
|
5
5
|
const url_1 = require("url");
|
|
6
6
|
const api_call_interface_1 = require("../api-call-interface");
|
|
7
7
|
class ExternalApiCallVisitor {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.transformArgsToFetchCall = (endpointNode) => ({
|
|
10
|
-
type: api_call_interface_1.ApiCallTypes.EXTERNAL,
|
|
11
|
-
url: endpointNode.value,
|
|
12
|
-
line: endpointNode.loc.start.line,
|
|
13
|
-
column: endpointNode.loc.start.column
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
8
|
visit(node, parent, callback) {
|
|
17
9
|
switch (node.type) {
|
|
18
10
|
case typescript_estree_1.AST_NODE_TYPES.CallExpression:
|
|
19
11
|
if (node.callee.type === typescript_estree_1.AST_NODE_TYPES.Identifier && node.callee.name === 'fetch') {
|
|
20
12
|
const [endpointNode] = node.arguments;
|
|
21
|
-
if (
|
|
13
|
+
if (endpointNode?.type === typescript_estree_1.AST_NODE_TYPES.Literal && typeof endpointNode?.value === 'string') {
|
|
22
14
|
if (endpointNode.value.trim() !== '') {
|
|
23
15
|
const fetchCall = this.transformArgsToFetchCall(endpointNode);
|
|
24
16
|
callback(fetchCall);
|
|
@@ -29,7 +21,7 @@ class ExternalApiCallVisitor {
|
|
|
29
21
|
case typescript_estree_1.AST_NODE_TYPES.MemberExpression:
|
|
30
22
|
if (node.property.type === typescript_estree_1.AST_NODE_TYPES.Identifier &&
|
|
31
23
|
node.property.name === 'fetch' &&
|
|
32
|
-
|
|
24
|
+
parent?.type === typescript_estree_1.AST_NODE_TYPES.CallExpression) {
|
|
33
25
|
const [endpointNode] = parent.arguments;
|
|
34
26
|
if (this.endpointCanBeChecked(endpointNode)) {
|
|
35
27
|
const fetchCall = this.transformArgsToFetchCall(endpointNode);
|
|
@@ -45,15 +37,21 @@ class ExternalApiCallVisitor {
|
|
|
45
37
|
thunk();
|
|
46
38
|
return false;
|
|
47
39
|
}
|
|
48
|
-
catch
|
|
40
|
+
catch {
|
|
49
41
|
return true;
|
|
50
42
|
}
|
|
51
43
|
};
|
|
52
44
|
const isPathOnlyUrl = (s) => throws(() => new url_1.URL(s)) && !throws(() => new url_1.URL(s, 'http://example'));
|
|
53
|
-
return (
|
|
54
|
-
typeof
|
|
45
|
+
return (endpointNode?.type === typescript_estree_1.AST_NODE_TYPES.Literal &&
|
|
46
|
+
typeof endpointNode?.value === 'string' &&
|
|
55
47
|
endpointNode.value.trim() !== '' &&
|
|
56
48
|
!isPathOnlyUrl(endpointNode.value));
|
|
57
49
|
}
|
|
50
|
+
transformArgsToFetchCall = (endpointNode) => ({
|
|
51
|
+
type: api_call_interface_1.ApiCallTypes.EXTERNAL,
|
|
52
|
+
url: endpointNode.value,
|
|
53
|
+
line: endpointNode.loc.start.line,
|
|
54
|
+
column: endpointNode.loc.start.column
|
|
55
|
+
});
|
|
58
56
|
}
|
|
59
57
|
exports.ExternalApiCallVisitor = ExternalApiCallVisitor;
|
|
@@ -4,12 +4,13 @@ exports.ImageUrlVisitor = void 0;
|
|
|
4
4
|
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
|
5
5
|
const api_call_interface_1 = require("../api-call-interface");
|
|
6
6
|
class ImageUrlVisitor {
|
|
7
|
+
static ABSOLUTE_URL_REGEX = /^(?:[a-z+]+:)?\/\//i;
|
|
7
8
|
visit(node, _parent, callback) {
|
|
8
9
|
const imageUrlNode = this.getImageUrlNode(node);
|
|
9
10
|
if (!imageUrlNode) {
|
|
10
11
|
return undefined;
|
|
11
12
|
}
|
|
12
|
-
if (
|
|
13
|
+
if (imageUrlNode?.type === typescript_estree_1.AST_NODE_TYPES.JSXAttribute) {
|
|
13
14
|
const imageUrlAttribute = imageUrlNode.value;
|
|
14
15
|
if (imageUrlAttribute &&
|
|
15
16
|
imageUrlAttribute.type === typescript_estree_1.AST_NODE_TYPES.Literal &&
|
|
@@ -54,4 +55,3 @@ class ImageUrlVisitor {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
exports.ImageUrlVisitor = ImageUrlVisitor;
|
|
57
|
-
ImageUrlVisitor.ABSOLUTE_URL_REGEX = /^(?:[a-z+]+:)?\/\//i;
|
|
@@ -17,7 +17,7 @@ class ProductNodeVisitor {
|
|
|
17
17
|
case typescript_estree_1.AST_NODE_TYPES.MemberExpression:
|
|
18
18
|
if (node.property.type === typescript_estree_1.AST_NODE_TYPES.Identifier &&
|
|
19
19
|
node.property.name.match(REQUEST_PRODUCT_REGEX) &&
|
|
20
|
-
|
|
20
|
+
parent?.type === typescript_estree_1.AST_NODE_TYPES.CallExpression) {
|
|
21
21
|
const apiCallProblem = this.checkProductApiCallArgs(node.property.name, parent.arguments);
|
|
22
22
|
apiCallProblem && callback(apiCallProblem);
|
|
23
23
|
}
|
|
@@ -78,10 +78,10 @@ class ProductNodeVisitor {
|
|
|
78
78
|
if (!endpointString) {
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
|
-
const methodProperty = optionsNode
|
|
81
|
+
const methodProperty = optionsNode?.properties.find((property) => property.type === typescript_estree_1.AST_NODE_TYPES.Property &&
|
|
82
82
|
property.key.type === typescript_estree_1.AST_NODE_TYPES.Identifier &&
|
|
83
83
|
property.key.name === 'method');
|
|
84
|
-
const methodValue =
|
|
84
|
+
const methodValue = methodProperty?.value.type === typescript_estree_1.AST_NODE_TYPES.Literal && typeof methodProperty.value.value === 'string'
|
|
85
85
|
? methodProperty.value.value
|
|
86
86
|
: 'GET';
|
|
87
87
|
return {
|
|
@@ -9,7 +9,10 @@ class StorageAPINodeVisitor {
|
|
|
9
9
|
node.specifiers.forEach((specifier) => {
|
|
10
10
|
if (specifier.type === 'ImportSpecifier' && specifier.local.type === 'Identifier') {
|
|
11
11
|
if (specifier.loc && specifier.local.name === 'storage') {
|
|
12
|
-
const storageApiCall =
|
|
12
|
+
const storageApiCall = {
|
|
13
|
+
type: api_call_interface_1.ApiCallTypes.STORAGE,
|
|
14
|
+
...specifier.loc.start
|
|
15
|
+
};
|
|
13
16
|
callback(storageApiCall);
|
|
14
17
|
}
|
|
15
18
|
}
|
|
@@ -8,7 +8,11 @@ class UIHookNodeVisitor {
|
|
|
8
8
|
if (node.type === typescript_estree_1.AST_NODE_TYPES.ImportDeclaration) {
|
|
9
9
|
node.specifiers.forEach((specifier) => {
|
|
10
10
|
if (specifier.loc && specifier.local.name.match(/^use(Space|Content|Issue)Property/)) {
|
|
11
|
-
const hookApiCall =
|
|
11
|
+
const hookApiCall = {
|
|
12
|
+
type: api_call_interface_1.ApiCallTypes.HOOK,
|
|
13
|
+
hookName: specifier.local.name,
|
|
14
|
+
...specifier.loc.start
|
|
15
|
+
};
|
|
12
16
|
callback(hookApiCall);
|
|
13
17
|
}
|
|
14
18
|
});
|
|
@@ -7,6 +7,7 @@ const invoke_remote_interface_1 = require("./invoke-remote-interface");
|
|
|
7
7
|
const invoke_remote_verifier_1 = require("./verifiers/invoke-remote-verifier");
|
|
8
8
|
const invoke_remote_call_visitor_1 = require("./visitors/invoke-remote-call-visitor");
|
|
9
9
|
class InvokeRemoteLinter extends base_linter_1.default {
|
|
10
|
+
manifest;
|
|
10
11
|
constructor(environment, manifest, logger) {
|
|
11
12
|
super(environment, logger);
|
|
12
13
|
this.manifest = manifest;
|
|
@@ -4,13 +4,6 @@ exports.InvokeRemoteCallVisitor = void 0;
|
|
|
4
4
|
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
|
|
5
5
|
const invoke_remote_interface_1 = require("../invoke-remote-interface");
|
|
6
6
|
class InvokeRemoteCallVisitor {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.transformArgsToInvokeRemoteCall = (remoteKeyNode) => ({
|
|
9
|
-
remoteKey: remoteKeyNode.value,
|
|
10
|
-
line: remoteKeyNode.loc.start.line,
|
|
11
|
-
column: remoteKeyNode.loc.start.column
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
7
|
visit(node, parent, callback) {
|
|
15
8
|
switch (node.type) {
|
|
16
9
|
case typescript_estree_1.AST_NODE_TYPES.ImportDeclaration:
|
|
@@ -38,7 +31,11 @@ class InvokeRemoteCallVisitor {
|
|
|
38
31
|
if (node.callee.type === typescript_estree_1.AST_NODE_TYPES.Identifier) {
|
|
39
32
|
const [remoteKeyNode] = node.arguments;
|
|
40
33
|
if (this.remoteKeyCanBeChecked(remoteKeyNode)) {
|
|
41
|
-
const invokeRemoteCall =
|
|
34
|
+
const invokeRemoteCall = {
|
|
35
|
+
...this.transformArgsToInvokeRemoteCall(remoteKeyNode),
|
|
36
|
+
type: invoke_remote_interface_1.InvokeRemoteTypes.DIRECT_CALL,
|
|
37
|
+
calleeName: node.callee.name
|
|
38
|
+
};
|
|
42
39
|
callback(invokeRemoteCall);
|
|
43
40
|
}
|
|
44
41
|
}
|
|
@@ -47,10 +44,14 @@ class InvokeRemoteCallVisitor {
|
|
|
47
44
|
if (node.property.type === typescript_estree_1.AST_NODE_TYPES.Identifier &&
|
|
48
45
|
node.object.type === typescript_estree_1.AST_NODE_TYPES.Identifier &&
|
|
49
46
|
node.property.name === invoke_remote_interface_1.INVOKE_REMOTE_FUNCTION_NAME &&
|
|
50
|
-
|
|
47
|
+
parent?.type === typescript_estree_1.AST_NODE_TYPES.CallExpression) {
|
|
51
48
|
const [remoteKeyNode] = parent.arguments;
|
|
52
49
|
if (this.remoteKeyCanBeChecked(remoteKeyNode)) {
|
|
53
|
-
const invokeRemoteMemberCall =
|
|
50
|
+
const invokeRemoteMemberCall = {
|
|
51
|
+
...this.transformArgsToInvokeRemoteCall(remoteKeyNode),
|
|
52
|
+
type: invoke_remote_interface_1.InvokeRemoteTypes.MEMBER_CALL,
|
|
53
|
+
objectName: node.object.name
|
|
54
|
+
};
|
|
54
55
|
callback(invokeRemoteMemberCall);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -58,9 +59,14 @@ class InvokeRemoteCallVisitor {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
remoteKeyCanBeChecked(remoteKeyNode) {
|
|
61
|
-
return (
|
|
62
|
-
typeof
|
|
62
|
+
return (remoteKeyNode?.type === typescript_estree_1.AST_NODE_TYPES.Literal &&
|
|
63
|
+
typeof remoteKeyNode?.value === 'string' &&
|
|
63
64
|
remoteKeyNode.value.trim() !== '');
|
|
64
65
|
}
|
|
66
|
+
transformArgsToInvokeRemoteCall = (remoteKeyNode) => ({
|
|
67
|
+
remoteKey: remoteKeyNode.value,
|
|
68
|
+
line: remoteKeyNode.loc.start.line,
|
|
69
|
+
column: remoteKeyNode.loc.start.column
|
|
70
|
+
});
|
|
65
71
|
}
|
|
66
72
|
exports.InvokeRemoteCallVisitor = InvokeRemoteCallVisitor;
|
|
@@ -4,6 +4,8 @@ exports.BaseLintIssueVerifier = void 0;
|
|
|
4
4
|
const linter_interface_1 = require("../linter-interface");
|
|
5
5
|
const cli_shared_1 = require("@forge/cli-shared");
|
|
6
6
|
class BaseLintIssueVerifier {
|
|
7
|
+
environment;
|
|
8
|
+
manifest;
|
|
7
9
|
constructor(environment, manifest) {
|
|
8
10
|
this.environment = environment;
|
|
9
11
|
this.manifest = manifest;
|
package/out/parse/parser.js
CHANGED
|
@@ -10,12 +10,20 @@ const createParseOptions = (absoluteFilePath, fileSystemReader) => {
|
|
|
10
10
|
const tsconfigOptions = fileSystemReader.fileExists(TSCONFIG_PATH)
|
|
11
11
|
? { tsconfigRootDir: process.cwd(), project: TSCONFIG_PATH }
|
|
12
12
|
: {};
|
|
13
|
-
return
|
|
13
|
+
return {
|
|
14
|
+
errorOnTypeScriptSyntacticAndSemanticIssues: true,
|
|
15
|
+
loc: true,
|
|
16
|
+
filePath: absoluteFilePath,
|
|
17
|
+
...tsconfigOptions
|
|
18
|
+
};
|
|
14
19
|
};
|
|
15
20
|
exports.createParseOptions = createParseOptions;
|
|
16
21
|
const tsParser = async (code, filepath, parseOptions) => {
|
|
17
22
|
const absoluteFilePath = path_1.default.resolve(process.cwd(), filepath);
|
|
18
|
-
const parseResult = (0, typescript_estree_1.parseAndGenerateServices)(code,
|
|
23
|
+
const parseResult = (0, typescript_estree_1.parseAndGenerateServices)(code, {
|
|
24
|
+
...(0, exports.createParseOptions)(absoluteFilePath, new cli_shared_1.FileSystemReader()),
|
|
25
|
+
...parseOptions
|
|
26
|
+
});
|
|
19
27
|
return {
|
|
20
28
|
parsed: parseResult.ast,
|
|
21
29
|
traverse: (options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/lint",
|
|
3
|
-
"version": "5.1.1-next.
|
|
3
|
+
"version": "5.1.1-next.6",
|
|
4
4
|
"description": "Linting for forge apps",
|
|
5
5
|
"main": "out/index.js",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"eslint-plugin-import": "^2.29.1"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@forge/cli-shared": "5.0.0-next.
|
|
21
|
+
"@forge/cli-shared": "5.0.0-next.6",
|
|
22
22
|
"@forge/egress": "1.2.13",
|
|
23
|
-
"@forge/manifest": "7.3.0-next.
|
|
23
|
+
"@forge/manifest": "7.3.0-next.5",
|
|
24
24
|
"@typescript-eslint/typescript-estree": "^5.62.0",
|
|
25
25
|
"array.prototype.flatmap": "^1.3.2",
|
|
26
26
|
"atlassian-openapi": "^1.0.18",
|