aws-iam-language-server 0.0.13 → 0.0.14
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/package.json +1 -1
- package/readme.md +11 -0
- package/src/handlers/completion/action-value.d.ts +2 -0
- package/src/handlers/completion/action-value.js +1 -1
- package/src/handlers/completion/condition-key.d.ts +2 -0
- package/src/handlers/completion/condition-key.js +3 -3
- package/src/handlers/completion/index.d.ts +2 -2
- package/src/handlers/completion/index.js +4 -4
- package/src/handlers/completion/principal-identifier-completions.d.ts +1 -1
- package/src/handlers/diagnostics/resource.js +4 -4
- package/src/handlers/diagnostics/utils.js +1 -13
- package/src/handlers/document-link/document-link.js +1 -3
- package/src/handlers/hover/action-value.d.ts +3 -0
- package/src/handlers/hover/action-value.js +21 -0
- package/src/handlers/hover/condition-block.d.ts +3 -0
- package/src/handlers/hover/condition-block.js +18 -0
- package/src/handlers/hover/condition-key.d.ts +3 -0
- package/src/handlers/hover/condition-key.js +53 -0
- package/src/handlers/hover/condition-operator.d.ts +3 -0
- package/src/handlers/hover/condition-operator.js +14 -0
- package/src/handlers/hover/effect-value.d.ts +3 -0
- package/src/handlers/hover/effect-value.js +17 -0
- package/src/handlers/hover/index.d.ts +3 -0
- package/src/handlers/hover/index.js +69 -0
- package/src/handlers/hover/principal-block.d.ts +3 -0
- package/src/handlers/hover/principal-block.js +17 -0
- package/src/handlers/hover/principal-type.d.ts +3 -0
- package/src/handlers/hover/principal-type.js +25 -0
- package/src/handlers/hover/principal-typed-value.d.ts +3 -0
- package/src/handlers/hover/principal-typed-value.js +118 -0
- package/src/handlers/hover/principal-value.d.ts +3 -0
- package/src/handlers/hover/principal-value.js +13 -0
- package/src/handlers/hover/resource-value.d.ts +3 -0
- package/src/handlers/hover/resource-value.js +45 -0
- package/src/handlers/hover/statement-block.d.ts +3 -0
- package/src/handlers/hover/statement-block.js +14 -0
- package/src/handlers/hover/statement-key.d.ts +3 -0
- package/src/handlers/hover/statement-key.js +14 -0
- package/src/lib/iam-policy/arn.d.ts +17 -0
- package/src/lib/iam-policy/arn.js +77 -0
- package/src/lib/iam-policy/location.d.ts +31 -1
- package/src/lib/iam-policy/location.js +56 -19
- package/src/lib/iam-policy/reference/services.d.ts +5 -3
- package/src/lib/iam-policy/reference/services.js +58 -19
- package/src/lib/iam-policy/reference/types.d.ts +5 -0
- package/src/lib/treesitter/base.d.ts +3 -8
- package/src/lib/treesitter/base.js +3 -3
- package/src/lib/treesitter/hcl.js +32 -24
- package/src/lib/treesitter/json.js +14 -11
- package/src/lib/treesitter/yaml.js +30 -27
- package/src/server.js +5 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { MarkupKind } from 'vscode-languageserver';
|
|
2
|
+
import { parseArn } from "../../lib/iam-policy/arn.js";
|
|
3
|
+
import { ServiceReference } from "../../lib/iam-policy/reference/services.js";
|
|
4
|
+
export function handleResourceValueHover(location) {
|
|
5
|
+
if (location.value === '*') {
|
|
6
|
+
return {
|
|
7
|
+
range: location.range,
|
|
8
|
+
contents: {
|
|
9
|
+
kind: MarkupKind.Markdown,
|
|
10
|
+
value: 'Matches **all resources**.\n\nSome actions do not support resource-level permissions and require `"Resource": "*"`.',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
const parsed = parseArn(location.value);
|
|
15
|
+
if (!parsed)
|
|
16
|
+
return null;
|
|
17
|
+
const resources = ServiceReference.getResources(parsed);
|
|
18
|
+
if (resources.length === 0)
|
|
19
|
+
return null;
|
|
20
|
+
const lines = [];
|
|
21
|
+
for (let i = 0; i < resources.length; i++) {
|
|
22
|
+
lines.push(`**${parsed.service} ${resources[i].name}**`);
|
|
23
|
+
if (resources[i].arnFormats.length > 0) {
|
|
24
|
+
lines.push('\n**ARNs**');
|
|
25
|
+
for (const format of resources[i].arnFormats) {
|
|
26
|
+
lines.push(`- \`${format}\``);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (resources[i].conditionKeys.length > 0) {
|
|
30
|
+
lines.push('\n**Condition keys**');
|
|
31
|
+
for (const key of resources[i].conditionKeys) {
|
|
32
|
+
lines.push(`- \`${key}\``);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (i + 1 !== resources.length)
|
|
36
|
+
lines.push('\n---\n');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
range: location.range,
|
|
40
|
+
contents: {
|
|
41
|
+
kind: MarkupKind.Markdown,
|
|
42
|
+
value: lines.join('\n'),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MarkupKind } from 'vscode-languageserver';
|
|
2
|
+
import { findHclElement } from "../completion/statement-block.js";
|
|
3
|
+
export function handleStatementBlockHover(location) {
|
|
4
|
+
const element = findHclElement(location.value);
|
|
5
|
+
if (!element)
|
|
6
|
+
return null;
|
|
7
|
+
return {
|
|
8
|
+
range: location.range,
|
|
9
|
+
contents: {
|
|
10
|
+
kind: MarkupKind.Markdown,
|
|
11
|
+
value: element.description,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MarkupKind } from 'vscode-languageserver';
|
|
2
|
+
import { StatementKeys } from "../../lib/iam-policy/statement-keys.js";
|
|
3
|
+
export function handleStatementKeyHover(_connection, location) {
|
|
4
|
+
const statementKey = StatementKeys[location.value];
|
|
5
|
+
if (!statementKey)
|
|
6
|
+
return null;
|
|
7
|
+
return {
|
|
8
|
+
range: location.range,
|
|
9
|
+
contents: {
|
|
10
|
+
kind: MarkupKind.Markdown,
|
|
11
|
+
value: statementKey.description,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ArnParts = {
|
|
2
|
+
partition: string;
|
|
3
|
+
service: string;
|
|
4
|
+
region: string;
|
|
5
|
+
account: string;
|
|
6
|
+
resource: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Parse an ARN string into its structural components.
|
|
10
|
+
* Returns null if the string is not a valid ARN structure (fewer than 6 colon-separated segments).
|
|
11
|
+
* Everything after the 5th colon is treated as the resource portion.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseArn(arn: string): ArnParts | null;
|
|
14
|
+
/**
|
|
15
|
+
* Check if a parsed user ARN matches a parsed template ARN.
|
|
16
|
+
*/
|
|
17
|
+
export declare function arnMatches(userArn: ArnParts, templateArn: ArnParts): boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse an ARN string into its structural components.
|
|
3
|
+
* Returns null if the string is not a valid ARN structure (fewer than 6 colon-separated segments).
|
|
4
|
+
* Everything after the 5th colon is treated as the resource portion.
|
|
5
|
+
*/
|
|
6
|
+
export function parseArn(arn) {
|
|
7
|
+
const segments = arn.split(':');
|
|
8
|
+
if (segments.length < 6)
|
|
9
|
+
return null;
|
|
10
|
+
if (segments[0] !== 'arn')
|
|
11
|
+
return null;
|
|
12
|
+
return {
|
|
13
|
+
partition: segments[1],
|
|
14
|
+
service: segments[2],
|
|
15
|
+
region: segments[3],
|
|
16
|
+
account: segments[4],
|
|
17
|
+
resource: segments.slice(5).join(':'),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const placeholderPattern = /\$\{[^}]+\}/;
|
|
21
|
+
/**
|
|
22
|
+
* Check if two ARN tokens match. A token matches if:
|
|
23
|
+
* - Either is a wildcard (`*`) or contains `?`
|
|
24
|
+
* - Either is a placeholder (`${...}`)
|
|
25
|
+
* - Both are empty (valid for region/account in some ARNs)
|
|
26
|
+
* - They are literally equal
|
|
27
|
+
*/
|
|
28
|
+
function tokensMatch(a, b) {
|
|
29
|
+
if (a === b)
|
|
30
|
+
return true;
|
|
31
|
+
if (a === '*' || b === '*')
|
|
32
|
+
return true;
|
|
33
|
+
if (a.includes('?') || b.includes('?'))
|
|
34
|
+
return true;
|
|
35
|
+
// Placeholders expect a value — don't match empty strings
|
|
36
|
+
if (a !== '' && b !== '' && (placeholderPattern.test(a) || placeholderPattern.test(b)))
|
|
37
|
+
return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Tokenize a resource string by splitting on both `:` and `/` delimiters.
|
|
42
|
+
* Preserves the delimiter as a separate token so structural comparison
|
|
43
|
+
* handles both `function:name` and `role/name` patterns.
|
|
44
|
+
*/
|
|
45
|
+
function tokenizeResource(resource) {
|
|
46
|
+
return resource.split(/(?<=[:\/])|(?=[:\/])/);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if the resource portion of a user ARN matches a template resource pattern.
|
|
50
|
+
* Splits on `:` and `/` and compares tokens positionally.
|
|
51
|
+
*/
|
|
52
|
+
function resourceMatches(userResource, templateResource) {
|
|
53
|
+
const userTokens = tokenizeResource(userResource);
|
|
54
|
+
const templateTokens = tokenizeResource(templateResource);
|
|
55
|
+
if (userTokens.length !== templateTokens.length) {
|
|
56
|
+
// A trailing `*` in the user ARN can match multiple template tokens
|
|
57
|
+
if (userTokens.length < templateTokens.length && userTokens[userTokens.length - 1] === '*') {
|
|
58
|
+
return userTokens.slice(0, -1).every((token, i) => tokensMatch(token, templateTokens[i]));
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return userTokens.every((token, i) => tokensMatch(token, templateTokens[i]));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if a parsed user ARN matches a parsed template ARN.
|
|
66
|
+
*/
|
|
67
|
+
export function arnMatches(userArn, templateArn) {
|
|
68
|
+
if (!tokensMatch(userArn.partition, templateArn.partition))
|
|
69
|
+
return false;
|
|
70
|
+
if (!tokensMatch(userArn.service, templateArn.service))
|
|
71
|
+
return false;
|
|
72
|
+
if (!tokensMatch(userArn.region, templateArn.region))
|
|
73
|
+
return false;
|
|
74
|
+
if (!tokensMatch(userArn.account, templateArn.account))
|
|
75
|
+
return false;
|
|
76
|
+
return resourceMatches(userArn.resource, templateArn.resource);
|
|
77
|
+
}
|
|
@@ -1,68 +1,98 @@
|
|
|
1
|
-
import type { CursorContext } from '../treesitter/base.ts';
|
|
1
|
+
import type { CursorContext, Range } from '../treesitter/base.ts';
|
|
2
2
|
export type StatementKeyLocation = {
|
|
3
3
|
type: 'statement-key';
|
|
4
4
|
partial: string;
|
|
5
|
+
value: string;
|
|
6
|
+
range?: Range;
|
|
5
7
|
};
|
|
6
8
|
export type StatementBlockLocation = {
|
|
7
9
|
type: 'statement-block';
|
|
8
10
|
partial: string;
|
|
11
|
+
value: string;
|
|
12
|
+
range?: Range;
|
|
9
13
|
};
|
|
10
14
|
export type EffectValueLocation = {
|
|
11
15
|
type: 'effect-value';
|
|
12
16
|
partial: string;
|
|
17
|
+
value: string;
|
|
18
|
+
range?: Range;
|
|
13
19
|
};
|
|
14
20
|
export type ActionValueLocation = {
|
|
15
21
|
type: 'action-value';
|
|
16
22
|
partial: string;
|
|
23
|
+
value: string;
|
|
24
|
+
range?: Range;
|
|
17
25
|
};
|
|
18
26
|
export type ResourceValueLocation = {
|
|
19
27
|
type: 'resource-value';
|
|
20
28
|
partial: string;
|
|
29
|
+
value: string;
|
|
30
|
+
range?: Range;
|
|
21
31
|
};
|
|
22
32
|
export type PrincipalValueLocation = {
|
|
23
33
|
type: 'principal-value';
|
|
24
34
|
partial: string;
|
|
35
|
+
value: string;
|
|
36
|
+
range?: Range;
|
|
25
37
|
};
|
|
26
38
|
export type PrincipalTypeLocation = {
|
|
27
39
|
type: 'principal-type';
|
|
28
40
|
partial: string;
|
|
41
|
+
value: string;
|
|
42
|
+
range?: Range;
|
|
29
43
|
};
|
|
30
44
|
export type PrincipalBlockLocation = {
|
|
31
45
|
type: 'principal-block';
|
|
32
46
|
partial: string;
|
|
47
|
+
value: string;
|
|
48
|
+
range?: Range;
|
|
33
49
|
};
|
|
34
50
|
export type PrincipalBlockTypeLocation = {
|
|
35
51
|
type: 'principal-block-type';
|
|
36
52
|
partial: string;
|
|
53
|
+
value: string;
|
|
54
|
+
range?: Range;
|
|
37
55
|
};
|
|
38
56
|
export type PrincipalBlockIdentifierLocation = {
|
|
39
57
|
type: 'principal-block-identifier';
|
|
40
58
|
principalType: string | null;
|
|
41
59
|
partial: string;
|
|
60
|
+
value: string;
|
|
61
|
+
range?: Range;
|
|
42
62
|
};
|
|
43
63
|
export type PrincipalTypedValueLocation = {
|
|
44
64
|
type: 'principal-typed-value';
|
|
45
65
|
principalType: string;
|
|
46
66
|
partial: string;
|
|
67
|
+
value: string;
|
|
68
|
+
range?: Range;
|
|
47
69
|
};
|
|
48
70
|
export type ConditionBlockLocation = {
|
|
49
71
|
type: 'condition-block';
|
|
50
72
|
partial: string;
|
|
73
|
+
value: string;
|
|
74
|
+
range?: Range;
|
|
51
75
|
};
|
|
52
76
|
export type ConditionOperatorLocation = {
|
|
53
77
|
type: 'condition-operator';
|
|
54
78
|
partial: string;
|
|
79
|
+
value: string;
|
|
80
|
+
range?: Range;
|
|
55
81
|
};
|
|
56
82
|
export type ConditionKeyLocation = {
|
|
57
83
|
type: 'condition-key';
|
|
58
84
|
operator: string;
|
|
59
85
|
partial: string;
|
|
86
|
+
value: string;
|
|
87
|
+
range?: Range;
|
|
60
88
|
};
|
|
61
89
|
export type ConditionValueLocation = {
|
|
62
90
|
type: 'condition-value';
|
|
63
91
|
operator: string;
|
|
64
92
|
key: string;
|
|
65
93
|
partial: string;
|
|
94
|
+
value: string;
|
|
95
|
+
range?: Range;
|
|
66
96
|
};
|
|
67
97
|
export type UnknownLocation = {
|
|
68
98
|
type: 'unknown';
|
|
@@ -16,66 +16,103 @@ export function resolvePolicyLocation(context) {
|
|
|
16
16
|
const statementKey = isHclBlock ? (snakeToPascal[keys[0]] ?? keys[0]) : keys[0];
|
|
17
17
|
if (keys.length === 0 && context.role === 'key') {
|
|
18
18
|
if (isHclBlock)
|
|
19
|
-
return { type: 'statement-block', partial: context.partial };
|
|
20
|
-
return { type: 'statement-key', partial: context.partial };
|
|
19
|
+
return { type: 'statement-block', partial: context.partial, value: context.value, range: context.range };
|
|
20
|
+
return { type: 'statement-key', partial: context.partial, value: context.value, range: context.range };
|
|
21
21
|
}
|
|
22
22
|
if (keys.length === 1 && context.role === 'value') {
|
|
23
23
|
if (statementKey === 'Effect')
|
|
24
|
-
return { type: 'effect-value', partial: context.partial };
|
|
24
|
+
return { type: 'effect-value', partial: context.partial, value: context.value, range: context.range };
|
|
25
25
|
if (statementKey === 'Action' || statementKey === 'NotAction')
|
|
26
|
-
return { type: 'action-value', partial: context.partial };
|
|
26
|
+
return { type: 'action-value', partial: context.partial, value: context.value, range: context.range };
|
|
27
27
|
if (statementKey === 'Resource' || statementKey === 'NotResource')
|
|
28
|
-
return { type: 'resource-value', partial: context.partial };
|
|
28
|
+
return { type: 'resource-value', partial: context.partial, value: context.value, range: context.range };
|
|
29
29
|
if (statementKey === 'Principal' || statementKey === 'NotPrincipal')
|
|
30
|
-
return { type: 'principal-value', partial: context.partial };
|
|
30
|
+
return { type: 'principal-value', partial: context.partial, value: context.value, range: context.range };
|
|
31
31
|
if (statementKey === 'Condition')
|
|
32
|
-
return { type: 'condition-operator', partial: context.partial };
|
|
32
|
+
return { type: 'condition-operator', partial: context.partial, value: context.value, range: context.range };
|
|
33
33
|
}
|
|
34
34
|
if (keys.length === 1 && context.role === 'key') {
|
|
35
35
|
if (statementKey === 'Principal' || statementKey === 'NotPrincipal') {
|
|
36
36
|
if (isHclBlock) {
|
|
37
|
-
return { type: 'principal-block', partial: context.partial };
|
|
37
|
+
return { type: 'principal-block', partial: context.partial, value: context.value, range: context.range };
|
|
38
38
|
}
|
|
39
|
-
return { type: 'principal-type', partial: context.partial };
|
|
39
|
+
return { type: 'principal-type', partial: context.partial, value: context.value, range: context.range };
|
|
40
40
|
}
|
|
41
41
|
if (statementKey === 'Condition') {
|
|
42
42
|
if (isHclBlock)
|
|
43
|
-
return { type: 'condition-block', partial: context.partial };
|
|
44
|
-
return { type: 'condition-operator', partial: context.partial };
|
|
43
|
+
return { type: 'condition-block', partial: context.partial, value: context.value, range: context.range };
|
|
44
|
+
return { type: 'condition-operator', partial: context.partial, value: context.value, range: context.range };
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
if ((keys.length === 2 || keys.length === 3) && context.role === 'value') {
|
|
48
48
|
if (statementKey === 'Principal' || statementKey === 'NotPrincipal') {
|
|
49
49
|
if (isHclBlock && keys[1] === 'type') {
|
|
50
|
-
return { type: 'principal-block-type', partial: context.partial };
|
|
50
|
+
return { type: 'principal-block-type', partial: context.partial, value: context.value, range: context.range };
|
|
51
51
|
}
|
|
52
52
|
if (isHclBlock && keys[1] === 'identifiers') {
|
|
53
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
type: 'principal-block-identifier',
|
|
55
|
+
principalType: keys[2] ?? null,
|
|
56
|
+
partial: context.partial,
|
|
57
|
+
value: context.value,
|
|
58
|
+
range: context.range,
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
if (keys.length === 2) {
|
|
56
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
type: 'principal-typed-value',
|
|
64
|
+
principalType: keys[1],
|
|
65
|
+
partial: context.partial,
|
|
66
|
+
value: context.value,
|
|
67
|
+
range: context.range,
|
|
68
|
+
};
|
|
57
69
|
}
|
|
58
70
|
}
|
|
59
71
|
}
|
|
60
72
|
if (keys.length === 2 && context.role === 'value') {
|
|
61
73
|
if (statementKey === 'Condition' && isHclBlock && keys[1] === 'test') {
|
|
62
|
-
return { type: 'condition-operator', partial: context.partial };
|
|
74
|
+
return { type: 'condition-operator', partial: context.partial, value: context.value, range: context.range };
|
|
63
75
|
}
|
|
64
76
|
if (statementKey === 'Condition' && isHclBlock && keys[1] === 'variable') {
|
|
65
|
-
return {
|
|
77
|
+
return {
|
|
78
|
+
type: 'condition-key',
|
|
79
|
+
operator: '',
|
|
80
|
+
partial: context.partial,
|
|
81
|
+
value: context.value,
|
|
82
|
+
range: context.range,
|
|
83
|
+
};
|
|
66
84
|
}
|
|
67
85
|
if (statementKey === 'Condition' && !isHclBlock) {
|
|
68
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
type: 'condition-key',
|
|
88
|
+
operator: keys[1],
|
|
89
|
+
partial: context.partial,
|
|
90
|
+
value: context.value,
|
|
91
|
+
range: context.range,
|
|
92
|
+
};
|
|
69
93
|
}
|
|
70
94
|
}
|
|
71
95
|
if (keys.length === 2 && context.role === 'key') {
|
|
72
96
|
if (statementKey === 'Condition') {
|
|
73
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
type: 'condition-key',
|
|
99
|
+
operator: keys[1],
|
|
100
|
+
partial: context.partial,
|
|
101
|
+
value: context.value,
|
|
102
|
+
range: context.range,
|
|
103
|
+
};
|
|
74
104
|
}
|
|
75
105
|
}
|
|
76
106
|
if (keys.length === 3 && context.role === 'value') {
|
|
77
107
|
if (statementKey === 'Condition') {
|
|
78
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
type: 'condition-value',
|
|
110
|
+
operator: keys[1],
|
|
111
|
+
key: keys[2],
|
|
112
|
+
partial: context.partial,
|
|
113
|
+
value: context.value,
|
|
114
|
+
range: context.range,
|
|
115
|
+
};
|
|
79
116
|
}
|
|
80
117
|
}
|
|
81
118
|
return { type: 'unknown' };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type ArnParts } from '../arn.ts';
|
|
2
|
+
import type { Action, ConditionKey, GlobalConditionKey, ResourceDef, ServiceData } from './types.ts';
|
|
2
3
|
export declare class ServiceReference {
|
|
3
4
|
#private;
|
|
4
|
-
static getServiceData(service: string): ServiceData;
|
|
5
|
-
static getServicePrincipals(): string
|
|
5
|
+
static getServiceData(service: string): ServiceData | undefined;
|
|
6
|
+
static getServicePrincipals(): Array<string>;
|
|
6
7
|
static getAllActions(): Array<string>;
|
|
7
8
|
static getAllServices(): Array<string>;
|
|
8
9
|
static getActionsForService(service: string): Array<Action>;
|
|
@@ -13,6 +14,7 @@ export declare class ServiceReference {
|
|
|
13
14
|
static getGlobalConditionKeys(): Array<GlobalConditionKey>;
|
|
14
15
|
static getAction(action: string): Action | undefined;
|
|
15
16
|
static getConditionKey(service: string, keyName: string): ConditionKey | undefined;
|
|
17
|
+
static getResources(arn: ArnParts): ResourceDef[];
|
|
16
18
|
static getResourcesForActions(actions: string[]): Map<string, {
|
|
17
19
|
service: string;
|
|
18
20
|
name: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { arnMatches, parseArn } from "../arn.js";
|
|
2
3
|
export class ServiceReference {
|
|
3
4
|
static #serviceDataMap = {};
|
|
4
5
|
static #allActions;
|
|
@@ -7,30 +8,53 @@ export class ServiceReference {
|
|
|
7
8
|
static #globalConditionKeys;
|
|
8
9
|
static getServiceData(service) {
|
|
9
10
|
if (!ServiceReference.#serviceDataMap[service]) {
|
|
10
|
-
|
|
11
|
+
try {
|
|
12
|
+
ServiceReference.#serviceDataMap[service] = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/services/${service}.json`, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
11
17
|
}
|
|
12
18
|
return ServiceReference.#serviceDataMap[service];
|
|
13
19
|
}
|
|
14
20
|
static getServicePrincipals() {
|
|
15
21
|
if (!ServiceReference.#servicePrincipals) {
|
|
16
|
-
|
|
22
|
+
try {
|
|
23
|
+
ServiceReference.#servicePrincipals = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/service-principals.json`, 'utf-8'));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
17
28
|
}
|
|
18
29
|
return ServiceReference.#servicePrincipals;
|
|
19
30
|
}
|
|
20
31
|
static getAllActions() {
|
|
21
32
|
if (!ServiceReference.#allActions) {
|
|
22
|
-
|
|
33
|
+
try {
|
|
34
|
+
ServiceReference.#allActions = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/actions.json`, 'utf-8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
23
39
|
}
|
|
24
40
|
return ServiceReference.#allActions;
|
|
25
41
|
}
|
|
26
42
|
static getAllServices() {
|
|
27
43
|
if (!ServiceReference.#allServices) {
|
|
28
|
-
|
|
44
|
+
try {
|
|
45
|
+
ServiceReference.#allServices = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/services.json`, 'utf-8'));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
29
50
|
}
|
|
30
51
|
return ServiceReference.#allServices;
|
|
31
52
|
}
|
|
32
53
|
static getActionsForService(service) {
|
|
33
|
-
|
|
54
|
+
const serviceData = ServiceReference.getServiceData(service);
|
|
55
|
+
if (!serviceData)
|
|
56
|
+
return [];
|
|
57
|
+
return Object.entries(serviceData.actions).map(([actionName, action]) => {
|
|
34
58
|
if (!action.name)
|
|
35
59
|
action.name = `${service}:${actionName}`;
|
|
36
60
|
return action;
|
|
@@ -41,6 +65,8 @@ export class ServiceReference {
|
|
|
41
65
|
for (const action of actions) {
|
|
42
66
|
const [service, actionName] = action.split(':');
|
|
43
67
|
const serviceData = ServiceReference.getServiceData(service);
|
|
68
|
+
if (!serviceData)
|
|
69
|
+
continue;
|
|
44
70
|
const actionDef = serviceData.actions[actionName];
|
|
45
71
|
if (actionDef?.conditionKeys) {
|
|
46
72
|
for (const keyName of actionDef.conditionKeys) {
|
|
@@ -56,33 +82,46 @@ export class ServiceReference {
|
|
|
56
82
|
}
|
|
57
83
|
static getGlobalConditionKeys() {
|
|
58
84
|
if (!ServiceReference.#globalConditionKeys) {
|
|
59
|
-
|
|
85
|
+
try {
|
|
86
|
+
ServiceReference.#globalConditionKeys = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/condition-keys/global.json`, 'utf-8'));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
60
91
|
}
|
|
61
92
|
return ServiceReference.#globalConditionKeys;
|
|
62
93
|
}
|
|
63
94
|
static getAction(action) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return ServiceReference.getServiceData(serviceName).actions[actionName];
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
95
|
+
const serviceName = action.split(':')[0];
|
|
96
|
+
const actionName = action.split(':')[1];
|
|
97
|
+
return ServiceReference.getServiceData(serviceName)?.actions[actionName];
|
|
72
98
|
}
|
|
73
99
|
static getConditionKey(service, keyName) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
return ServiceReference.getServiceData(service)?.conditionKeys[keyName];
|
|
101
|
+
}
|
|
102
|
+
static getResources(arn) {
|
|
103
|
+
const serviceData = ServiceReference.getServiceData(arn.service);
|
|
104
|
+
if (!serviceData)
|
|
105
|
+
return [];
|
|
106
|
+
const matches = [];
|
|
107
|
+
for (const resource of serviceData.resources) {
|
|
108
|
+
for (const format of resource.arnFormats) {
|
|
109
|
+
const templateArn = parseArn(format);
|
|
110
|
+
if (templateArn && arnMatches(arn, templateArn)) {
|
|
111
|
+
matches.push(resource);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
79
115
|
}
|
|
116
|
+
return matches;
|
|
80
117
|
}
|
|
81
118
|
static getResourcesForActions(actions) {
|
|
82
119
|
const resources = new Map();
|
|
83
120
|
for (const action of actions) {
|
|
84
121
|
const [service, actionName] = action.split(':');
|
|
85
122
|
const serviceData = ServiceReference.getServiceData(service);
|
|
123
|
+
if (!serviceData)
|
|
124
|
+
continue;
|
|
86
125
|
const actionDef = serviceData.actions[actionName];
|
|
87
126
|
if (!actionDef?.resources)
|
|
88
127
|
continue;
|
|
@@ -70,6 +70,11 @@ export type Action = {
|
|
|
70
70
|
operationUrl?: string;
|
|
71
71
|
iamUrl?: string;
|
|
72
72
|
};
|
|
73
|
+
export type ResourceDef = {
|
|
74
|
+
name: string;
|
|
75
|
+
arnFormats: Array<string>;
|
|
76
|
+
conditionKeys: Array<string>;
|
|
77
|
+
};
|
|
73
78
|
export type ConditionKey = {
|
|
74
79
|
types: Array<string>;
|
|
75
80
|
description?: string;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
+
import type { Position, Range } from 'vscode-languageserver';
|
|
1
2
|
import type { Language, Node, Tree } from 'web-tree-sitter';
|
|
2
|
-
export type Position
|
|
3
|
-
line: number;
|
|
4
|
-
column: number;
|
|
5
|
-
};
|
|
6
|
-
export type Range = {
|
|
7
|
-
start: Position;
|
|
8
|
-
end: Position;
|
|
9
|
-
};
|
|
3
|
+
export type { Position, Range };
|
|
10
4
|
export type StatementValue = {
|
|
11
5
|
text: string;
|
|
12
6
|
range: Range;
|
|
@@ -34,6 +28,7 @@ export type CursorContext = {
|
|
|
34
28
|
role: 'key' | 'value';
|
|
35
29
|
partial: string;
|
|
36
30
|
value: string;
|
|
31
|
+
range?: Range;
|
|
37
32
|
policyFormat: PolicyFormat;
|
|
38
33
|
};
|
|
39
34
|
export type StatementContext = {
|
|
@@ -2,8 +2,8 @@ import { Parser } from 'web-tree-sitter';
|
|
|
2
2
|
await Parser.init();
|
|
3
3
|
export function nodeRange(node) {
|
|
4
4
|
return {
|
|
5
|
-
start: { line: node.startPosition.row,
|
|
6
|
-
end: { line: node.endPosition.row,
|
|
5
|
+
start: { line: node.startPosition.row, character: node.startPosition.column },
|
|
6
|
+
end: { line: node.endPosition.row, character: node.endPosition.column },
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
9
|
export class TreeBase {
|
|
@@ -40,7 +40,7 @@ export class TreeBase {
|
|
|
40
40
|
return null;
|
|
41
41
|
const node = tree.rootNode.descendantForPosition({
|
|
42
42
|
row: position.line,
|
|
43
|
-
column: position.
|
|
43
|
+
column: position.character,
|
|
44
44
|
});
|
|
45
45
|
return node;
|
|
46
46
|
}
|