aws-iam-language-server 0.0.15 → 0.0.16

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.15",
3
+ "version": "0.0.16",
4
4
  "type": "module",
5
5
  "bin": "./src/server.js",
6
6
  "publisher": "MichaelBarney",
@@ -16,6 +16,78 @@
16
16
  "onLanguage:terraform"
17
17
  ],
18
18
  "icon": "./images/iam.png",
19
+ "contributes": {
20
+ "configuration": {
21
+ "title": "AWS IAM Language Server",
22
+ "properties": {
23
+ "aws-iam-language-server.diagnostics.DUPLICATE_KEY.enabled": {
24
+ "type": "boolean",
25
+ "default": true,
26
+ "description": "Warn on duplicate statement keys."
27
+ },
28
+ "aws-iam-language-server.diagnostics.UNRECOGNIZED_KEY.enabled": {
29
+ "type": "boolean",
30
+ "default": true,
31
+ "description": "Warn on unrecognized entries in a statement."
32
+ },
33
+ "aws-iam-language-server.diagnostics.MISSING_EFFECT.enabled": {
34
+ "type": "boolean",
35
+ "default": true,
36
+ "description": "Warn when a statement is missing the required Effect entry."
37
+ },
38
+ "aws-iam-language-server.diagnostics.MISSING_ACTION.enabled": {
39
+ "type": "boolean",
40
+ "default": true,
41
+ "description": "Warn when a statement is missing the required Action or NotAction entry."
42
+ },
43
+ "aws-iam-language-server.diagnostics.MISSING_RESOURCE_OR_PRINCIPAL.enabled": {
44
+ "type": "boolean",
45
+ "default": true,
46
+ "description": "Warn when a statement is missing both Resource/NotResource and Principal/NotPrincipal."
47
+ },
48
+ "aws-iam-language-server.diagnostics.INVALID_EFFECT.enabled": {
49
+ "type": "boolean",
50
+ "default": true,
51
+ "description": "Warn when Effect value is not Allow or Deny."
52
+ },
53
+ "aws-iam-language-server.diagnostics.DUPLICATE_SID.enabled": {
54
+ "type": "boolean",
55
+ "default": true,
56
+ "description": "Warn on duplicate Sid values across statements."
57
+ },
58
+ "aws-iam-language-server.diagnostics.INVALID_SID.enabled": {
59
+ "type": "boolean",
60
+ "default": true,
61
+ "description": "Warn when Sid contains invalid characters."
62
+ },
63
+ "aws-iam-language-server.diagnostics.UNRECOGNIZED_ACTION.enabled": {
64
+ "type": "boolean",
65
+ "default": true,
66
+ "description": "Warn when an action does not match any known IAM action."
67
+ },
68
+ "aws-iam-language-server.diagnostics.DEPENDENT_ACTION.enabled": {
69
+ "type": "boolean",
70
+ "default": true,
71
+ "description": "Warn when an action requires dependent actions that aren't granted in the same statement."
72
+ },
73
+ "aws-iam-language-server.diagnostics.INVALID_PARTITION.enabled": {
74
+ "type": "boolean",
75
+ "default": true,
76
+ "description": "Warn on invalid or missing partition in a resource ARN."
77
+ },
78
+ "aws-iam-language-server.diagnostics.INVALID_REGION.enabled": {
79
+ "type": "boolean",
80
+ "default": true,
81
+ "description": "Warn on invalid region for the specified partition."
82
+ },
83
+ "aws-iam-language-server.diagnostics.INVALID_ACCOUNT.enabled": {
84
+ "type": "boolean",
85
+ "default": true,
86
+ "description": "Warn when account ID is not 12 digits."
87
+ }
88
+ }
89
+ }
90
+ },
19
91
  "main": "./src/extension.js",
20
92
  "scripts": {
21
93
  "start": "node src/server.ts --stdio",
package/readme.md CHANGED
@@ -15,6 +15,15 @@ It supports policies written in
15
15
 
16
16
  Install the [extension](https://marketplace.visualstudio.com/items?itemName=MichaelBarney.aws-iam-language-server).
17
17
 
18
+ #### Config
19
+
20
+ ```json
21
+ {
22
+ // replace ${DIAGNOSTIC_RULE} with a diganostic rule id, like DEPENDENT_ACTION
23
+ "aws-iam-language-server.diagnostics.${DIAGNOSTIC_RULE}.enabled": true
24
+ }
25
+ ```
26
+
18
27
  ### Neovim, etc
19
28
 
20
29
  You can install the language server globally with npm:
@@ -30,6 +39,15 @@ vim.lsp.config("aws-iam-language-server", {
30
39
  cmd = { "aws-iam-language-server", "--stdio" },
31
40
  filetypes = { "yaml", "yaml.cloudformation", "json", "json.cloudformation", "terraform", "tofu" },
32
41
  root_markers = { ".git" },
42
+ -- optional, only if you want to override the defaults
43
+ settings = {
44
+ ["aws-iam-language-server"] = {
45
+ diagnostics = {
46
+ -- replace ${DIAGNOSTIC_RULE} with a diganostic rule id, like DEPENDENT_ACTION
47
+ ${DIAGNOSTIC_RULE} = { enabled = false },
48
+ },
49
+ },
50
+ },
33
51
  })
34
52
 
35
53
  vim.lsp.enable("aws-iam-language-server")
@@ -82,3 +100,4 @@ This language server will provide diagnostics for some IAM policy issues, includ
82
100
  - effect has a valid value
83
101
  - defined actions are valid, or wildcards resolve to valid actions
84
102
  - arn parts are valid (partition, region, account id)
103
+ - dependent actions (`ecs:RunTask` requires `iam:PassRole`)
package/src/extension.js CHANGED
@@ -9,6 +9,9 @@ export function activate(context) {
9
9
  };
10
10
  client = new LanguageClient('aws-iam-language-server', 'AWS IAM Language Server', serverOptions, {
11
11
  documentSelector: [{ language: 'json' }, { language: 'yaml' }, { language: 'terraform' }, { language: 'opentofu' }],
12
+ synchronize: {
13
+ configurationSection: 'aws-iam-language-server',
14
+ },
12
15
  });
13
16
  client.start();
14
17
  }
@@ -1,6 +1,8 @@
1
- import type { Diagnostic } from 'vscode-languageclient';
2
- import type { StatementEntry } from '../../lib/treesitter/base.ts';
1
+ import { type Diagnostic } from 'vscode-languageserver';
2
+ import type { PolicyDocumentNode, StatementEntry } from '../../lib/treesitter/base.ts';
3
3
  import { ElementValidator } from './base.ts';
4
4
  export declare class ActionValidator extends ElementValidator {
5
+ #private;
6
+ constructor(policyDocument: PolicyDocumentNode, actionKeys: Set<string>);
5
7
  validate(entry: StatementEntry): Array<Diagnostic>;
6
8
  }
@@ -1,14 +1,57 @@
1
+ import { DiagnosticSeverity } from 'vscode-languageserver';
2
+ import { isRuleEnabled } from "../../lib/config.js";
3
+ import { ServiceReference } from "../../lib/iam-policy/reference/services.js";
1
4
  import { expandActionPattern } from "../../lib/iam-policy/wildcard.js";
2
5
  import { ElementValidator } from "./base.js";
3
6
  import { createDiagnostic } from "./utils.js";
4
7
  export class ActionValidator extends ElementValidator {
8
+ #policyDocument;
9
+ #actionKeys;
10
+ #expandedActions = null;
11
+ constructor(policyDocument, actionKeys) {
12
+ super();
13
+ this.#policyDocument = policyDocument;
14
+ this.#actionKeys = actionKeys;
15
+ }
5
16
  validate(entry) {
6
17
  const diagnostics = super.validate(entry);
7
18
  for (const value of entry.values) {
8
- if (expandActionPattern(value.text).length === 0) {
9
- diagnostics.push(createDiagnostic(`Unrecognized action "${value.text}"`, value.range));
19
+ const expanded = expandActionPattern(value.text);
20
+ if (isRuleEnabled('UNRECOGNIZED_ACTION')) {
21
+ if (expanded.length === 0) {
22
+ diagnostics.push(createDiagnostic('UNRECOGNIZED_ACTION', `Unrecognized action "${value.text}"`, value.range));
23
+ continue;
24
+ }
25
+ }
26
+ if (isRuleEnabled('DEPENDENT_ACTION')) {
27
+ const allActions = this.#getExpandedActions();
28
+ for (const action of expanded) {
29
+ const actionDef = ServiceReference.getAction(action);
30
+ if (!actionDef?.dependentActions)
31
+ continue;
32
+ const missing = actionDef.dependentActions.filter((dep) => !allActions.has(dep.toLowerCase()));
33
+ if (missing.length > 0) {
34
+ diagnostics.push(createDiagnostic('DEPENDENT_ACTION', `"${action}" requires dependent action${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`, value.range, DiagnosticSeverity.Warning));
35
+ }
36
+ }
10
37
  }
11
38
  }
12
39
  return diagnostics;
13
40
  }
41
+ #getExpandedActions() {
42
+ if (this.#expandedActions)
43
+ return this.#expandedActions;
44
+ this.#expandedActions = new Set();
45
+ for (const statement of this.#policyDocument.statements) {
46
+ const actionEntry = statement.entries.find((e) => this.#actionKeys.has(e.key));
47
+ if (!actionEntry)
48
+ continue;
49
+ for (const value of actionEntry.values) {
50
+ for (const action of expandActionPattern(value.text)) {
51
+ this.#expandedActions.add(action.toLowerCase());
52
+ }
53
+ }
54
+ }
55
+ return this.#expandedActions;
56
+ }
14
57
  }
@@ -1,3 +1,4 @@
1
+ import { isRuleEnabled } from "../../lib/config.js";
1
2
  import { createDiagnostic } from "./utils.js";
2
3
  export class ElementValidator {
3
4
  #validated;
@@ -6,8 +7,8 @@ export class ElementValidator {
6
7
  }
7
8
  validate(entry) {
8
9
  const diagnostics = [];
9
- if (this.#validated) {
10
- diagnostics.push(createDiagnostic('duplicate statement key', entry.keyRange));
10
+ if (this.#validated && isRuleEnabled('DUPLICATE_KEY')) {
11
+ diagnostics.push(createDiagnostic('DUPLICATE_KEY', 'duplicate statement key', entry.keyRange));
11
12
  }
12
13
  this.#validated = true;
13
14
  return diagnostics;
@@ -1,3 +1,4 @@
1
+ import { isRuleEnabled } from "../../lib/config.js";
1
2
  import { ActionValidator } from "./action.js";
2
3
  import { ConditionValidator } from "./condition.js";
3
4
  import { EffectValidator } from "./effect.js";
@@ -30,7 +31,7 @@ async function handleStandardDiagnostics(policyDocument) {
30
31
  const sidValidator = new SidValidator();
31
32
  const effectValidator = new EffectValidator();
32
33
  const principalValidator = new PrincipalValidator();
33
- const actionValidator = new ActionValidator();
34
+ const actionValidator = new ActionValidator(policyDocument, new Set(['Action', 'NotAction']));
34
35
  const resourceValidator = new ResourceValidator();
35
36
  const conditionValidator = new ConditionValidator();
36
37
  for (const statement of policyDocument.statements) {
@@ -54,18 +55,20 @@ async function handleStandardDiagnostics(policyDocument) {
54
55
  else if (entry.key === 'Condition') {
55
56
  diagnostics = diagnostics.concat(conditionValidator.validate(entry));
56
57
  }
57
- else {
58
- diagnostics.push(createDiagnostic(`Unrecognized entry "${entry.key}" in statement`, entry.keyRange));
58
+ else if (isRuleEnabled('UNRECOGNIZED_KEY')) {
59
+ diagnostics.push(createDiagnostic('UNRECOGNIZED_KEY', `Unrecognized entry "${entry.key}" in statement`, entry.keyRange));
59
60
  }
60
61
  }
61
- if (!effectValidator.isValidated()) {
62
- diagnostics.push(createDiagnostic(`Missing required "Effect" entry in statement`, statement.range));
62
+ if (!effectValidator.isValidated() && isRuleEnabled('MISSING_EFFECT')) {
63
+ diagnostics.push(createDiagnostic('MISSING_EFFECT', 'Missing required "Effect" entry in statement', statement.range));
63
64
  }
64
- if (!actionValidator.isValidated()) {
65
- diagnostics.push(createDiagnostic(`Missing required "Action" or "NotAction" entry in statement`, statement.range));
65
+ if (!actionValidator.isValidated() && isRuleEnabled('MISSING_ACTION')) {
66
+ diagnostics.push(createDiagnostic('MISSING_ACTION', 'Missing required "Action" or "NotAction" entry in statement', statement.range));
66
67
  }
67
- if (!resourceValidator.isValidated() && !principalValidator.isValidated()) {
68
- diagnostics.push(createDiagnostic(`Missing required "Resource"/"NotResource" or "Principal"/"NotPrincipal" entry in statement`, statement.range));
68
+ if (!resourceValidator.isValidated() &&
69
+ !principalValidator.isValidated() &&
70
+ isRuleEnabled('MISSING_RESOURCE_OR_PRINCIPAL')) {
71
+ diagnostics.push(createDiagnostic('MISSING_RESOURCE_OR_PRINCIPAL', 'Missing required "Resource"/"NotResource" or "Principal"/"NotPrincipal" entry in statement', statement.range));
69
72
  }
70
73
  [sidValidator, effectValidator, principalValidator, actionValidator, resourceValidator, conditionValidator].forEach((x) => {
71
74
  x.resetForStatement();
@@ -78,7 +81,7 @@ async function handleHclBlockDiagnostics(policyDocument) {
78
81
  const sidValidator = new SidValidator();
79
82
  const effectValidator = new EffectValidator();
80
83
  const principalValidator = new PrincipalValidator();
81
- const actionValidator = new ActionValidator();
84
+ const actionValidator = new ActionValidator(policyDocument, new Set(['actions', 'not_actions']));
82
85
  const resourceValidator = new ResourceValidator();
83
86
  const conditionValidator = new ConditionValidator();
84
87
  for (const statement of policyDocument.statements) {
@@ -102,18 +105,20 @@ async function handleHclBlockDiagnostics(policyDocument) {
102
105
  else if (entry.key === 'condition') {
103
106
  diagnostics = diagnostics.concat(conditionValidator.validate(entry));
104
107
  }
105
- else {
106
- diagnostics.push(createDiagnostic(`Unrecognized entry "${entry.key}" in statement`, entry.keyRange));
108
+ else if (isRuleEnabled('UNRECOGNIZED_KEY')) {
109
+ diagnostics.push(createDiagnostic('UNRECOGNIZED_KEY', `Unrecognized entry "${entry.key}" in statement`, entry.keyRange));
107
110
  }
108
111
  }
109
- if (!effectValidator.isValidated()) {
110
- diagnostics.push(createDiagnostic(`Missing required "effect" entry in statement`, statement.range));
112
+ if (!effectValidator.isValidated() && isRuleEnabled('MISSING_EFFECT')) {
113
+ diagnostics.push(createDiagnostic('MISSING_EFFECT', 'Missing required "effect" entry in statement', statement.range));
111
114
  }
112
- if (!actionValidator.isValidated()) {
113
- diagnostics.push(createDiagnostic(`Missing required "actions" or "not_actions" entry in statement`, statement.range));
115
+ if (!actionValidator.isValidated() && isRuleEnabled('MISSING_ACTION')) {
116
+ diagnostics.push(createDiagnostic('MISSING_ACTION', 'Missing required "actions" or "not_actions" entry in statement', statement.range));
114
117
  }
115
- if (!resourceValidator.isValidated() && !principalValidator.isValidated()) {
116
- diagnostics.push(createDiagnostic(`Missing required "resources"/"not_resources" or "principals"/"not_principals" entry in statement`, statement.range));
118
+ if (!resourceValidator.isValidated() &&
119
+ !principalValidator.isValidated() &&
120
+ isRuleEnabled('MISSING_RESOURCE_OR_PRINCIPAL')) {
121
+ diagnostics.push(createDiagnostic('MISSING_RESOURCE_OR_PRINCIPAL', 'Missing required "resources"/"not_resources" or "principals"/"not_principals" entry in statement', statement.range));
117
122
  }
118
123
  [sidValidator, effectValidator, principalValidator, actionValidator, resourceValidator, conditionValidator].forEach((x) => {
119
124
  x.resetForStatement();
@@ -1,11 +1,12 @@
1
+ import { isRuleEnabled } from "../../lib/config.js";
1
2
  import { ElementValidator } from "./base.js";
2
3
  import { createDiagnostic } from "./utils.js";
3
4
  export class EffectValidator extends ElementValidator {
4
5
  validate(entry) {
5
6
  const diagnostics = super.validate(entry);
6
7
  const value = entry.values[0]?.text;
7
- if (value !== 'Allow' && value !== 'Deny') {
8
- diagnostics.push(createDiagnostic(`effect value must be either "Allow" or "Deny"`, entry.valueRange));
8
+ if (isRuleEnabled('INVALID_EFFECT') && value !== 'Allow' && value !== 'Deny') {
9
+ diagnostics.push(createDiagnostic('INVALID_EFFECT', 'effect value must be either "Allow" or "Deny"', entry.valueRange));
9
10
  }
10
11
  return diagnostics;
11
12
  }
@@ -1,3 +1,4 @@
1
+ import { isRuleEnabled } from "../../lib/config.js";
1
2
  import { isRegionValidForPartition, isValidPartition, partitions } from "../../lib/iam-policy/partitions.js";
2
3
  import { ElementValidator } from "./base.js";
3
4
  import { createDiagnostic } from "./utils.js";
@@ -22,28 +23,28 @@ function validateArn(value) {
22
23
  if (!text.startsWith('arn:'))
23
24
  return [];
24
25
  const segments = text.split(':');
25
- if (segments.length > 1) {
26
+ if (segments.length > 1 && isRuleEnabled('INVALID_PARTITION')) {
26
27
  const partition = segments[1];
27
28
  if (partition === '') {
28
- diagnostics.push(createDiagnostic('partition is required', segmentRange(value, 1)));
29
+ diagnostics.push(createDiagnostic('INVALID_PARTITION', 'partition is required', segmentRange(value, 1)));
29
30
  }
30
31
  else if (partition !== '*' && !Object.keys(partitions).includes(partition)) {
31
- diagnostics.push(createDiagnostic(`partition must be one of: ${[...validPartitions].join(',')}`, segmentRange(value, 1)));
32
+ diagnostics.push(createDiagnostic('INVALID_PARTITION', `partition must be one of: ${[...validPartitions].join(',')}`, segmentRange(value, 1)));
32
33
  }
33
34
  }
34
- if (segments.length > 3) {
35
+ if (segments.length > 3 && isRuleEnabled('INVALID_REGION')) {
35
36
  const partition = segments[1];
36
37
  const region = segments[3];
37
38
  if (isValidPartition(partition)) {
38
39
  if (region !== '*' && region !== '' && !isRegionValidForPartition(partition, region)) {
39
- diagnostics.push(createDiagnostic('invalid region for this partition', segmentRange(value, 3)));
40
+ diagnostics.push(createDiagnostic('INVALID_REGION', 'invalid region for this partition', segmentRange(value, 3)));
40
41
  }
41
42
  }
42
43
  }
43
- if (segments.length > 4) {
44
+ if (segments.length > 4 && isRuleEnabled('INVALID_ACCOUNT')) {
44
45
  const account = segments[4];
45
46
  if (account !== '*' && account !== '' && !accountIdPattern.test(account)) {
46
- diagnostics.push(createDiagnostic('expected account id to be 12 digits', segmentRange(value, 4)));
47
+ diagnostics.push(createDiagnostic('INVALID_ACCOUNT', 'expected account id to be 12 digits', segmentRange(value, 4)));
47
48
  }
48
49
  }
49
50
  return diagnostics;
@@ -1,3 +1,4 @@
1
+ import { isRuleEnabled } from "../../lib/config.js";
1
2
  import { ElementValidator } from "./base.js";
2
3
  import { createDiagnostic } from "./utils.js";
3
4
  const strictSidPattern = /^[A-Za-z0-9]*$/;
@@ -13,17 +14,17 @@ export class SidValidator extends ElementValidator {
13
14
  const sidValue = entry.values[0]?.text;
14
15
  if (!sidValue)
15
16
  return diagnostics;
16
- if (sidValue in this.#sids) {
17
- diagnostics.push(createDiagnostic(`Duplicate statement id value "${sidValue}"`, entry.valueRange));
18
- diagnostics.push(createDiagnostic(`Duplicate statement id value "${sidValue}"`, this.#sids[sidValue].valueRange));
17
+ if (sidValue in this.#sids && isRuleEnabled('DUPLICATE_SID')) {
18
+ diagnostics.push(createDiagnostic('DUPLICATE_SID', `Duplicate statement id value "${sidValue}"`, entry.valueRange));
19
+ diagnostics.push(createDiagnostic('DUPLICATE_SID', `Duplicate statement id value "${sidValue}"`, this.#sids[sidValue].valueRange));
19
20
  }
20
21
  this.#sids[sidValue] = entry;
21
22
  const pattern = isResourcePolicy ? resourcePolicySidPattern : strictSidPattern;
22
- if (!pattern.test(sidValue)) {
23
+ if (isRuleEnabled('INVALID_SID') && !pattern.test(sidValue)) {
23
24
  const message = isResourcePolicy
24
25
  ? 'Sid must contain only ASCII letters (A-Z, a-z), digits (0-9), and spaces'
25
26
  : 'Sid must contain only ASCII letters (A-Z, a-z) and digits (0-9)';
26
- diagnostics.push(createDiagnostic(message, entry.values[0].range));
27
+ diagnostics.push(createDiagnostic('INVALID_SID', message, entry.values[0].range));
27
28
  }
28
29
  return diagnostics;
29
30
  }
@@ -1,3 +1,4 @@
1
- import type { Diagnostic } from 'vscode-languageclient';
1
+ import { type Diagnostic, DiagnosticSeverity } from 'vscode-languageserver';
2
+ import type { DiagnosticRuleId } from '../../lib/diagnostics.ts';
2
3
  import type { Range } from '../../lib/treesitter/base.ts';
3
- export declare function createDiagnostic(message: string, range: Range): Diagnostic;
4
+ export declare function createDiagnostic(ruleId: DiagnosticRuleId, message: string, range: Range, severity?: DiagnosticSeverity): Diagnostic;
@@ -1,6 +1,9 @@
1
- export function createDiagnostic(message, range) {
1
+ import { DiagnosticSeverity } from 'vscode-languageserver';
2
+ export function createDiagnostic(ruleId, message, range, severity = DiagnosticSeverity.Error) {
2
3
  return {
3
4
  source: 'aws-iam-language-server',
5
+ severity,
6
+ code: ruleId,
4
7
  message,
5
8
  range,
6
9
  };
@@ -0,0 +1,11 @@
1
+ import { type DiagnosticRuleId } from './diagnostics.ts';
2
+ export type DiagnosticRuleConfig = {
3
+ enabled: boolean;
4
+ };
5
+ export type ServerConfig = {
6
+ diagnostics: Record<DiagnosticRuleId, DiagnosticRuleConfig>;
7
+ };
8
+ export declare function getConfig(): ServerConfig;
9
+ export declare function isRuleEnabled(ruleId: DiagnosticRuleId): boolean;
10
+ export declare function resetConfig(): void;
11
+ export declare function updateConfig(settings: Record<string, unknown> | undefined): void;
@@ -0,0 +1,34 @@
1
+ import { diagnosticRules } from "./diagnostics.js";
2
+ function createDefaults() {
3
+ const diagnostics = {};
4
+ for (const key of Object.keys(diagnosticRules)) {
5
+ diagnostics[key] = { enabled: true };
6
+ }
7
+ return { diagnostics };
8
+ }
9
+ const config = createDefaults();
10
+ export function getConfig() {
11
+ return config;
12
+ }
13
+ export function isRuleEnabled(ruleId) {
14
+ return config.diagnostics[ruleId].enabled;
15
+ }
16
+ export function resetConfig() {
17
+ const defaults = createDefaults();
18
+ for (const key of Object.keys(diagnosticRules)) {
19
+ config.diagnostics[key] = defaults.diagnostics[key];
20
+ }
21
+ }
22
+ export function updateConfig(settings) {
23
+ if (!settings)
24
+ return;
25
+ const diagnostics = settings.diagnostics;
26
+ if (!diagnostics)
27
+ return;
28
+ for (const key of Object.keys(diagnosticRules)) {
29
+ const ruleConfig = diagnostics[key];
30
+ if (ruleConfig && typeof ruleConfig.enabled === 'boolean') {
31
+ config.diagnostics[key].enabled = ruleConfig.enabled;
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,45 @@
1
+ export type DiagnosticRule = {
2
+ description: string;
3
+ };
4
+ export declare const diagnosticRules: {
5
+ readonly DUPLICATE_KEY: {
6
+ readonly description: "Duplicate statement key";
7
+ };
8
+ readonly UNRECOGNIZED_KEY: {
9
+ readonly description: "Unrecognized entry in statement";
10
+ };
11
+ readonly MISSING_EFFECT: {
12
+ readonly description: "Missing required Effect entry";
13
+ };
14
+ readonly MISSING_ACTION: {
15
+ readonly description: "Missing required Action or NotAction entry";
16
+ };
17
+ readonly MISSING_RESOURCE_OR_PRINCIPAL: {
18
+ readonly description: "Missing required Resource/NotResource or Principal/NotPrincipal entry";
19
+ };
20
+ readonly INVALID_EFFECT: {
21
+ readonly description: "Effect value must be Allow or Deny";
22
+ };
23
+ readonly DUPLICATE_SID: {
24
+ readonly description: "Duplicate Sid value across statements";
25
+ };
26
+ readonly INVALID_SID: {
27
+ readonly description: "Sid contains invalid characters";
28
+ };
29
+ readonly UNRECOGNIZED_ACTION: {
30
+ readonly description: "Action does not match any known IAM action";
31
+ };
32
+ readonly DEPENDENT_ACTION: {
33
+ readonly description: "Action requires dependent actions not granted in the same statement";
34
+ };
35
+ readonly INVALID_PARTITION: {
36
+ readonly description: "Invalid or missing partition in resource ARN";
37
+ };
38
+ readonly INVALID_REGION: {
39
+ readonly description: "Invalid region for the specified partition";
40
+ };
41
+ readonly INVALID_ACCOUNT: {
42
+ readonly description: "Account ID must be 12 digits";
43
+ };
44
+ };
45
+ export type DiagnosticRuleId = keyof typeof diagnosticRules;
@@ -0,0 +1,41 @@
1
+ export const diagnosticRules = {
2
+ DUPLICATE_KEY: {
3
+ description: 'Duplicate statement key',
4
+ },
5
+ UNRECOGNIZED_KEY: {
6
+ description: 'Unrecognized entry in statement',
7
+ },
8
+ MISSING_EFFECT: {
9
+ description: 'Missing required Effect entry',
10
+ },
11
+ MISSING_ACTION: {
12
+ description: 'Missing required Action or NotAction entry',
13
+ },
14
+ MISSING_RESOURCE_OR_PRINCIPAL: {
15
+ description: 'Missing required Resource/NotResource or Principal/NotPrincipal entry',
16
+ },
17
+ INVALID_EFFECT: {
18
+ description: 'Effect value must be Allow or Deny',
19
+ },
20
+ DUPLICATE_SID: {
21
+ description: 'Duplicate Sid value across statements',
22
+ },
23
+ INVALID_SID: {
24
+ description: 'Sid contains invalid characters',
25
+ },
26
+ UNRECOGNIZED_ACTION: {
27
+ description: 'Action does not match any known IAM action',
28
+ },
29
+ DEPENDENT_ACTION: {
30
+ description: 'Action requires dependent actions not granted in the same statement',
31
+ },
32
+ INVALID_PARTITION: {
33
+ description: 'Invalid or missing partition in resource ARN',
34
+ },
35
+ INVALID_REGION: {
36
+ description: 'Invalid region for the specified partition',
37
+ },
38
+ INVALID_ACCOUNT: {
39
+ description: 'Account ID must be 12 digits',
40
+ },
41
+ };
@@ -43,7 +43,7 @@ function tokensMatch(a, b) {
43
43
  * handles both `function:name` and `role/name` patterns.
44
44
  */
45
45
  function tokenizeResource(resource) {
46
- return resource.split(/(?<=[:\/])|(?=[:\/])/);
46
+ return resource.split(/(?<=[:/])|(?=[:/])/);
47
47
  }
48
48
  /**
49
49
  * Check if the resource portion of a user ARN matches a template resource pattern.
package/src/server.js CHANGED
@@ -5,11 +5,13 @@ import { handleCompletionRequest } from "./handlers/completion/index.js";
5
5
  import { diagnosticsHandler } from "./handlers/diagnostics/diagnostics.js";
6
6
  import { documentLinkHandler } from "./handlers/document-link/document-link.js";
7
7
  import { hoverHandler } from "./handlers/hover/index.js";
8
+ import { updateConfig } from "./lib/config.js";
8
9
  import { TreeManager } from "./lib/treesitter/manager.js";
9
10
  const connection = createConnection();
10
11
  const documents = new TextDocuments(TextDocument);
11
12
  const treeManager = new TreeManager(connection);
12
- connection.onInitialize(async () => {
13
+ connection.onInitialize(async (params) => {
14
+ updateConfig(params.initializationOptions);
13
15
  connection.console.log(`Started the AWS IAM Policy Language Server`);
14
16
  return {
15
17
  capabilities: {
@@ -22,6 +24,12 @@ connection.onInitialize(async () => {
22
24
  },
23
25
  };
24
26
  });
27
+ connection.onDidChangeConfiguration(async (params) => {
28
+ updateConfig(params.settings?.['aws-iam-language-server']);
29
+ for (const document of documents.all()) {
30
+ await diagnosticsHandler(document, treeManager, connection);
31
+ }
32
+ });
25
33
  documents.onDidOpen(async ({ document }) => {
26
34
  await treeManager.openDocument(document.uri, document.getText(), document.languageId);
27
35
  await diagnosticsHandler(document, treeManager, connection);