aws-iam-language-server 0.0.19 → 0.0.21

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-iam-language-server",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "type": "module",
5
5
  "bin": "./src/server.js",
6
6
  "publisher": "MichaelBarney",
@@ -1,4 +1,5 @@
1
1
  import { CompletionItemKind, MarkupKind } from 'vscode-languageserver';
2
+ import { splitArn } from "../../lib/iam-policy/arn.js";
2
3
  import { partitions } from "../../lib/iam-policy/partitions.js";
3
4
  import { principalTypes } from "../../lib/iam-policy/principals.js";
4
5
  import { partialRange } from "./index.js";
@@ -39,7 +40,7 @@ export function completePrincipalIdentifier(principalType, partial, position) {
39
40
  }
40
41
  return { items, isIncomplete: false };
41
42
  }
42
- const parts = partial.split(':');
43
+ const parts = splitArn(partial);
43
44
  if (parts.length === 1) {
44
45
  if (config.arn.length > 0 && 'arn'.startsWith(partial.toLowerCase())) {
45
46
  items.push({
@@ -70,7 +71,7 @@ export function completePrincipalIdentifier(principalType, partial, position) {
70
71
  }
71
72
  }
72
73
  else if (parts.length === 3) {
73
- const services = [...new Set(config.arn.map((pattern) => pattern.split(':')[2]))];
74
+ const services = [...new Set(config.arn.map((pattern) => splitArn(pattern)[2]))];
74
75
  for (const service of services) {
75
76
  if (`${parts[0]}:${parts[1]}:${service}`.toLowerCase().startsWith(partial.toLowerCase())) {
76
77
  items.push({ label: service, kind: CompletionItemKind.Enum });
@@ -79,8 +80,8 @@ export function completePrincipalIdentifier(principalType, partial, position) {
79
80
  }
80
81
  else if (parts.length === 4) {
81
82
  const service = parts[2];
82
- const serviceArns = config.arn.filter((pattern) => pattern.split(':')[2] === service);
83
- const hasRegionArn = serviceArns.some((pattern) => pattern.split(':')[3].length > 0);
83
+ const serviceArns = config.arn.filter((pattern) => splitArn(pattern)[2] === service);
84
+ const hasRegionArn = serviceArns.some((pattern) => splitArn(pattern)[3].length > 0);
84
85
  if (!hasRegionArn) {
85
86
  items.push({
86
87
  label: ':',
@@ -118,7 +119,7 @@ export function completePrincipalIdentifier(principalType, partial, position) {
118
119
  const region = parts[3];
119
120
  const account = parts[4];
120
121
  const matching = config.arn.filter((pattern) => {
121
- const patternParts = pattern.split(':');
122
+ const patternParts = splitArn(pattern);
122
123
  if (patternParts[2] !== service)
123
124
  return false;
124
125
  if (region.length > 0 !== patternParts[3].length > 0)
@@ -1,11 +1,12 @@
1
1
  import { CompletionItemKind, MarkupKind } from 'vscode-languageserver';
2
+ import { splitArn } from "../../lib/iam-policy/arn.js";
2
3
  import { partitions } from "../../lib/iam-policy/partitions.js";
3
4
  import { formatResourceDocumentation } from "../../lib/iam-policy/reference/documentation.js";
4
5
  import { ServiceReference } from "../../lib/iam-policy/reference/services.js";
5
6
  import { expandActionPattern } from "../../lib/iam-policy/wildcard.js";
6
7
  import { partialRange } from "./index.js";
7
8
  export function completeResourceValue(location, context) {
8
- const parts = location.partial.split(':');
9
+ const parts = splitArn(location.partial);
9
10
  const range = partialRange(context.position, location.partial.length);
10
11
  const items = [];
11
12
  const statement = context.handler.getStatementContext(context.uri, context.position);
@@ -127,7 +128,7 @@ export function completeResourceValue(location, context) {
127
128
  const resources = ServiceReference.getResourcesForActions(expandActionPattern(`${service}:*`));
128
129
  for (const resource of resources) {
129
130
  for (const arn of resource.arnFormats) {
130
- const patternParts = arn.split(':');
131
+ const patternParts = splitArn(arn);
131
132
  const patternRegion = patternParts[3];
132
133
  const patternAccount = patternParts[4];
133
134
  if (region.length > 0 !== patternRegion.length > 0)
@@ -1,11 +1,12 @@
1
1
  import { isRuleEnabled } from "../../lib/config.js";
2
+ import { splitArn } from "../../lib/iam-policy/arn.js";
2
3
  import { isRegionValidForPartition, isValidPartition, partitions } from "../../lib/iam-policy/partitions.js";
3
4
  import { ElementValidator } from "./base.js";
4
5
  import { createDiagnostic } from "./utils.js";
5
6
  const validPartitions = Object.keys(partitions);
6
7
  const accountIdPattern = /^\d{12}$/;
7
8
  function segmentRange(value, segmentIndex) {
8
- const segments = value.text.split(':');
9
+ const segments = splitArn(value.text);
9
10
  let offset = 0;
10
11
  for (let i = 0; i < segmentIndex; i++) {
11
12
  offset += segments[i].length + 1;
@@ -22,7 +23,7 @@ function validateArn(value) {
22
23
  const diagnostics = [];
23
24
  if (!text.startsWith('arn:'))
24
25
  return [];
25
- const segments = text.split(':');
26
+ const segments = splitArn(text);
26
27
  if (segments.length > 1 && isRuleEnabled('INVALID_PARTITION')) {
27
28
  const partition = segments[1];
28
29
  if (partition === '') {
@@ -1,4 +1,5 @@
1
1
  import { MarkupKind } from 'vscode-languageserver';
2
+ import { splitArn } from "../../lib/iam-policy/arn.js";
2
3
  import { formatPrincipalTypedValueDocumentation, principalTypedValues, } from "../../lib/iam-policy/reference/documentation.js";
3
4
  import { ServiceReference } from "../../lib/iam-policy/reference/services.js";
4
5
  export function handlePrincipalTypedValueHover(location) {
@@ -35,7 +36,7 @@ export function handlePrincipalTypedValueHover(location) {
35
36
  };
36
37
  }
37
38
  if (location.value.startsWith('arn:')) {
38
- const parts = location.value.split(':');
39
+ const parts = splitArn(location.value);
39
40
  const resource = parts.slice(5).join(':');
40
41
  if (resource.startsWith('root')) {
41
42
  return {
@@ -77,7 +78,7 @@ export function handlePrincipalTypedValueHover(location) {
77
78
  }
78
79
  if (principalType === 'Federated') {
79
80
  if (location.value.startsWith('arn:')) {
80
- const resource = location.value.split(':').slice(5).join(':');
81
+ const resource = splitArn(location.value).slice(5).join(':');
81
82
  if (resource.startsWith('oidc-provider/')) {
82
83
  return {
83
84
  range: location.range,
@@ -5,6 +5,11 @@ export type ArnParts = {
5
5
  account: string;
6
6
  resource: string;
7
7
  };
8
+ /**
9
+ * Split a string on `:` while preserving `${...}` placeholders intact.
10
+ * Colons inside `${...}` (e.g. `${AWS::AccountId}`) are not treated as delimiters.
11
+ */
12
+ export declare function splitArn(arn: string): string[];
8
13
  /**
9
14
  * Parse an ARN string into its structural components.
10
15
  * Returns null if the string is not a valid ARN structure (fewer than 6 colon-separated segments).
@@ -1,10 +1,39 @@
1
+ /**
2
+ * Split a string on `:` while preserving `${...}` placeholders intact.
3
+ * Colons inside `${...}` (e.g. `${AWS::AccountId}`) are not treated as delimiters.
4
+ */
5
+ export function splitArn(arn) {
6
+ const segments = [];
7
+ let current = '';
8
+ let depth = 0;
9
+ for (let i = 0; i < arn.length; i++) {
10
+ if (arn[i] === '$' && arn[i + 1] === '{') {
11
+ depth++;
12
+ current += '${';
13
+ i++;
14
+ }
15
+ else if (depth > 0 && arn[i] === '}') {
16
+ depth--;
17
+ current += '}';
18
+ }
19
+ else if (depth === 0 && arn[i] === ':') {
20
+ segments.push(current);
21
+ current = '';
22
+ }
23
+ else {
24
+ current += arn[i];
25
+ }
26
+ }
27
+ segments.push(current);
28
+ return segments;
29
+ }
1
30
  /**
2
31
  * Parse an ARN string into its structural components.
3
32
  * Returns null if the string is not a valid ARN structure (fewer than 6 colon-separated segments).
4
33
  * Everything after the 5th colon is treated as the resource portion.
5
34
  */
6
35
  export function parseArn(arn) {
7
- const segments = arn.split(':');
36
+ const segments = splitArn(arn);
8
37
  if (segments.length < 6)
9
38
  return null;
10
39
  if (segments[0] !== 'arn')