aws-iam-language-server 0.0.12 → 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.
Files changed (53) hide show
  1. package/package.json +1 -1
  2. package/readme.md +12 -0
  3. package/src/handlers/completion/action-value.d.ts +2 -0
  4. package/src/handlers/completion/action-value.js +1 -1
  5. package/src/handlers/completion/condition-key.d.ts +2 -0
  6. package/src/handlers/completion/condition-key.js +3 -3
  7. package/src/handlers/completion/index.d.ts +2 -2
  8. package/src/handlers/completion/index.js +4 -4
  9. package/src/handlers/completion/principal-identifier-completions.d.ts +1 -1
  10. package/src/handlers/diagnostics/resource.js +24 -9
  11. package/src/handlers/diagnostics/utils.js +1 -13
  12. package/src/handlers/document-link/document-link.js +1 -3
  13. package/src/handlers/hover/action-value.d.ts +3 -0
  14. package/src/handlers/hover/action-value.js +21 -0
  15. package/src/handlers/hover/condition-block.d.ts +3 -0
  16. package/src/handlers/hover/condition-block.js +18 -0
  17. package/src/handlers/hover/condition-key.d.ts +3 -0
  18. package/src/handlers/hover/condition-key.js +53 -0
  19. package/src/handlers/hover/condition-operator.d.ts +3 -0
  20. package/src/handlers/hover/condition-operator.js +14 -0
  21. package/src/handlers/hover/effect-value.d.ts +3 -0
  22. package/src/handlers/hover/effect-value.js +17 -0
  23. package/src/handlers/hover/index.d.ts +3 -0
  24. package/src/handlers/hover/index.js +69 -0
  25. package/src/handlers/hover/principal-block.d.ts +3 -0
  26. package/src/handlers/hover/principal-block.js +17 -0
  27. package/src/handlers/hover/principal-type.d.ts +3 -0
  28. package/src/handlers/hover/principal-type.js +25 -0
  29. package/src/handlers/hover/principal-typed-value.d.ts +3 -0
  30. package/src/handlers/hover/principal-typed-value.js +118 -0
  31. package/src/handlers/hover/principal-value.d.ts +3 -0
  32. package/src/handlers/hover/principal-value.js +13 -0
  33. package/src/handlers/hover/resource-value.d.ts +3 -0
  34. package/src/handlers/hover/resource-value.js +45 -0
  35. package/src/handlers/hover/statement-block.d.ts +3 -0
  36. package/src/handlers/hover/statement-block.js +14 -0
  37. package/src/handlers/hover/statement-key.d.ts +3 -0
  38. package/src/handlers/hover/statement-key.js +14 -0
  39. package/src/lib/iam-policy/arn.d.ts +17 -0
  40. package/src/lib/iam-policy/arn.js +77 -0
  41. package/src/lib/iam-policy/location.d.ts +31 -1
  42. package/src/lib/iam-policy/location.js +56 -19
  43. package/src/lib/iam-policy/partitions.d.ts +4 -0
  44. package/src/lib/iam-policy/partitions.js +6 -0
  45. package/src/lib/iam-policy/reference/services.d.ts +5 -3
  46. package/src/lib/iam-policy/reference/services.js +58 -19
  47. package/src/lib/iam-policy/reference/types.d.ts +5 -0
  48. package/src/lib/treesitter/base.d.ts +3 -8
  49. package/src/lib/treesitter/base.js +3 -3
  50. package/src/lib/treesitter/hcl.js +32 -24
  51. package/src/lib/treesitter/json.js +14 -11
  52. package/src/lib/treesitter/yaml.js +30 -27
  53. package/src/server.js +5 -3
@@ -0,0 +1,13 @@
1
+ import { MarkupKind } from 'vscode-languageserver';
2
+ export function handlePrincipalValueHover(location) {
3
+ if (location.value === '*') {
4
+ return {
5
+ range: location.range,
6
+ contents: {
7
+ kind: MarkupKind.Markdown,
8
+ value: '**Public (unauthenticated) access**\n\nMatches all principals, including anonymous users.\n\n> **Warning:** Combining `"Principal": "*"` with `"Effect": "Allow"` grants public access. Always scope with a `Condition` element unless public access is intended.',
9
+ },
10
+ };
11
+ }
12
+ return null;
13
+ }
@@ -0,0 +1,3 @@
1
+ import { type Hover } from 'vscode-languageserver';
2
+ import type { ResourceValueLocation } from '../../lib/iam-policy/location.ts';
3
+ export declare function handleResourceValueHover(location: ResourceValueLocation): Hover | null;
@@ -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,3 @@
1
+ import { type Hover } from 'vscode-languageserver';
2
+ import type { StatementBlockLocation } from '../../lib/iam-policy/location.ts';
3
+ export declare function handleStatementBlockHover(location: StatementBlockLocation): Hover | null;
@@ -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,3 @@
1
+ import { type Connection, type Hover } from 'vscode-languageserver';
2
+ import type { StatementKeyLocation } from '../../lib/iam-policy/location.ts';
3
+ export declare function handleStatementKeyHover(_connection: Connection, location: StatementKeyLocation): Hover | null;
@@ -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 { type: 'principal-block-identifier', principalType: keys[2] ?? null, partial: context.partial };
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 { type: 'principal-typed-value', principalType: keys[1], partial: context.partial };
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 { type: 'condition-key', operator: '', partial: context.partial };
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 { type: 'condition-key', operator: keys[1], partial: context.partial };
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 { type: 'condition-key', operator: keys[1], partial: context.partial };
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 { type: 'condition-value', operator: keys[1], key: keys[2], partial: context.partial };
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' };
@@ -114,3 +114,7 @@ export declare const partitions: {
114
114
  }];
115
115
  };
116
116
  };
117
+ type Partition = keyof typeof partitions;
118
+ export declare function isValidPartition(input: string): input is Partition;
119
+ export declare function isRegionValidForPartition<T extends Partition>(partition: T, input: string): input is (typeof partitions)[T]['regions'][number]['id'];
120
+ export {};
@@ -49,3 +49,9 @@ export const partitions = {
49
49
  ],
50
50
  },
51
51
  };
52
+ export function isValidPartition(input) {
53
+ return Object.keys(partitions).includes(input);
54
+ }
55
+ export function isRegionValidForPartition(partition, input) {
56
+ return partitions[partition].regions.some((r) => r.id === input);
57
+ }
@@ -1,8 +1,9 @@
1
- import type { Action, ConditionKey, GlobalConditionKey, ServiceData } from './types.ts';
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
- ServiceReference.#serviceDataMap[service] = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/services/${service}.json`, 'utf-8'));
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
- ServiceReference.#servicePrincipals = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/service-principals.json`, 'utf-8'));
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
- ServiceReference.#allActions = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/actions.json`, 'utf-8'));
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
- ServiceReference.#allServices = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/servicereference/services.json`, 'utf-8'));
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
- return Object.entries(ServiceReference.getServiceData(service).actions).map(([actionName, action]) => {
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
- ServiceReference.#globalConditionKeys = JSON.parse(readFileSync(`${import.meta.dirname}/../../../data/condition-keys/global.json`, 'utf-8'));
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
- try {
65
- const serviceName = action.split(':')[0];
66
- const actionName = action.split(':')[1];
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
- try {
75
- return ServiceReference.getServiceData(service).conditionKeys[keyName];
76
- }
77
- catch {
78
- return undefined;
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, column: node.startPosition.column },
6
- end: { line: node.endPosition.row, column: node.endPosition.column },
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.column,
43
+ column: position.character,
44
44
  });
45
45
  return node;
46
46
  }