@jbearak/sight 0.12.0 → 0.13.0

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.
@@ -52,7 +52,7 @@ var package_default;
52
52
  var init_package = __esm(() => {
53
53
  package_default = {
54
54
  name: "@jbearak/sight",
55
- version: "0.12.0",
55
+ version: "0.13.0",
56
56
  type: "module",
57
57
  workspaces: [
58
58
  "client"
@@ -633,6 +633,13 @@ function warn_unknown_key(key_path, raw_key, warn) {
633
633
  message: `Unknown project config key '${key_path}.${raw_key}'; ignoring`
634
634
  });
635
635
  }
636
+ function warn_compat_collision(canonical_path, alias_path, warn) {
637
+ warn?.({
638
+ code: "normalized-key-collision",
639
+ key_path: canonical_path,
640
+ message: `Both '${canonical_path}' and compatibility alias '${alias_path}' ` + `are set; using '${canonical_path}'`
641
+ });
642
+ }
636
643
  function normalize_severity_value(value, key_path, allowed, warn) {
637
644
  if (typeof value !== "string") {
638
645
  warn_invalid_value(key_path, value, warn);
@@ -734,25 +741,40 @@ function maybe_assign_object(target, key, value) {
734
741
  }
735
742
  }
736
743
  function map_diagnostics(raw, warn) {
737
- warn_unknown_keys(raw, ["enabled", "indentation", "severity"], "diagnostics", warn);
744
+ warn_unknown_keys(raw, ["enabled", "indentation", "severity", "undefinedVariableSeverity"], "diagnostics", warn);
738
745
  const mapped = {};
739
746
  assign_boolean(mapped, "enabled", raw, "enabled", "diagnostics.enabled", warn);
740
747
  assign_boolean(mapped, "indentation", raw, "indentation", "diagnostics.indentation", warn);
741
748
  const severity = object_value(raw, "severity", "diagnostics.severity", warn);
749
+ const mapped_severity = {};
750
+ let canonical_undefined_variable;
742
751
  if (severity) {
743
752
  warn_unknown_keys(severity, DIAGNOSTIC_SEVERITY_KEYS, "diagnostics.severity", warn);
744
- const mapped_severity = {};
745
753
  for (const my_key of DIAGNOSTIC_SEVERITY_KEYS) {
746
754
  const value = pick_key(severity, my_key, warn, `diagnostics.severity.${my_key}`);
747
755
  if (value !== undefined) {
756
+ if (my_key === "undefinedVariable") {
757
+ canonical_undefined_variable = value;
758
+ }
748
759
  const normalized = normalize_severity_value(value, `diagnostics.severity.${my_key}`, SEVERITIES, warn);
749
760
  if (normalized) {
750
761
  mapped_severity[my_key] = normalized;
751
762
  }
752
763
  }
753
764
  }
754
- maybe_assign_object(mapped, "severity", mapped_severity);
755
765
  }
766
+ const legacy_undefined_variable = pick_key(raw, "undefinedVariableSeverity", warn, "diagnostics.undefinedVariableSeverity");
767
+ if (legacy_undefined_variable !== undefined) {
768
+ if (canonical_undefined_variable !== undefined) {
769
+ warn_compat_collision("diagnostics.severity.undefinedVariable", "diagnostics.undefinedVariableSeverity", warn);
770
+ } else {
771
+ const normalized = normalize_severity_value(legacy_undefined_variable, "diagnostics.undefinedVariableSeverity", SEVERITIES, warn);
772
+ if (normalized) {
773
+ mapped_severity.undefinedVariable = normalized;
774
+ }
775
+ }
776
+ }
777
+ maybe_assign_object(mapped, "severity", mapped_severity);
756
778
  return Object.keys(mapped).length > 0 ? mapped : undefined;
757
779
  }
758
780
  function map_formatting(raw, warn) {
@@ -800,7 +822,12 @@ function map_cross_file(raw, warn) {
800
822
  "maxForwardDepth",
801
823
  "maxChainDepth",
802
824
  "maxCalleeRevalidations",
803
- "diagnostics"
825
+ "maxCachedFiles",
826
+ "maxCachedScopes",
827
+ "maxCachedForwardClosures",
828
+ "diagnostics",
829
+ "missingFileSeverity",
830
+ "caseMismatchSeverity"
804
831
  ], "crossFile", warn);
805
832
  const mapped = {};
806
833
  assign_boolean(mapped, "index_workspace", raw, "indexWorkspace", "crossFile.indexWorkspace", warn);
@@ -811,10 +838,15 @@ function map_cross_file(raw, warn) {
811
838
  assign_number(mapped, "max_forward_depth", raw, "maxForwardDepth", "crossFile.maxForwardDepth", warn);
812
839
  assign_number(mapped, "max_chain_depth", raw, "maxChainDepth", "crossFile.maxChainDepth", warn);
813
840
  assign_number(mapped, "max_callee_revalidations", raw, "maxCalleeRevalidations", "crossFile.maxCalleeRevalidations", warn);
841
+ assign_number(mapped, "max_cached_files", raw, "maxCachedFiles", "crossFile.maxCachedFiles", warn);
842
+ assign_number(mapped, "max_cached_scopes", raw, "maxCachedScopes", "crossFile.maxCachedScopes", warn);
843
+ assign_number(mapped, "max_cached_forward_closures", raw, "maxCachedForwardClosures", "crossFile.maxCachedForwardClosures", warn);
814
844
  const diagnostics = object_value(raw, "diagnostics", "crossFile.diagnostics", warn);
845
+ const mapped_diagnostics = {};
846
+ let canonical_missing_file;
847
+ let canonical_case_mismatch;
815
848
  if (diagnostics) {
816
849
  warn_unknown_keys(diagnostics, ["missingFile", "maxDepth", "callSiteIdentification", "caseMismatch"], "crossFile.diagnostics", warn);
817
- const mapped_diagnostics = {};
818
850
  for (const [public_key, internal_key] of [
819
851
  ["missingFile", "missing_file"],
820
852
  ["maxDepth", "max_depth"],
@@ -823,6 +855,9 @@ function map_cross_file(raw, warn) {
823
855
  const key_path = `crossFile.diagnostics.${public_key}`;
824
856
  const value = pick_key(diagnostics, public_key, warn, key_path);
825
857
  if (value !== undefined) {
858
+ if (public_key === "missingFile") {
859
+ canonical_missing_file = value;
860
+ }
826
861
  const normalized = normalize_severity_value(value, key_path, CROSS_FILE_SEVERITIES, warn);
827
862
  if (normalized) {
828
863
  mapped_diagnostics[internal_key] = normalized;
@@ -832,13 +867,36 @@ function map_cross_file(raw, warn) {
832
867
  const case_mismatch_key_path = "crossFile.diagnostics.caseMismatch";
833
868
  const case_mismatch_value = pick_key(diagnostics, "caseMismatch", warn, case_mismatch_key_path);
834
869
  if (case_mismatch_value !== undefined) {
870
+ canonical_case_mismatch = case_mismatch_value;
835
871
  const normalized = normalize_severity_value(case_mismatch_value, case_mismatch_key_path, CROSS_FILE_CASE_MISMATCH_SEVERITIES, warn);
836
872
  if (normalized) {
837
873
  mapped_diagnostics["case_mismatch"] = normalized;
838
874
  }
839
875
  }
840
- maybe_assign_object(mapped, "diagnostics", mapped_diagnostics);
841
876
  }
877
+ const legacy_missing_file = pick_key(raw, "missingFileSeverity", warn, "crossFile.missingFileSeverity");
878
+ if (legacy_missing_file !== undefined) {
879
+ if (canonical_missing_file !== undefined) {
880
+ warn_compat_collision("crossFile.diagnostics.missingFile", "crossFile.missingFileSeverity", warn);
881
+ } else {
882
+ const normalized = normalize_severity_value(legacy_missing_file, "crossFile.missingFileSeverity", CROSS_FILE_SEVERITIES, warn);
883
+ if (normalized) {
884
+ mapped_diagnostics.missing_file = normalized;
885
+ }
886
+ }
887
+ }
888
+ const legacy_case_mismatch = pick_key(raw, "caseMismatchSeverity", warn, "crossFile.caseMismatchSeverity");
889
+ if (legacy_case_mismatch !== undefined) {
890
+ if (canonical_case_mismatch !== undefined) {
891
+ warn_compat_collision("crossFile.diagnostics.caseMismatch", "crossFile.caseMismatchSeverity", warn);
892
+ } else {
893
+ const normalized = normalize_severity_value(legacy_case_mismatch, "crossFile.caseMismatchSeverity", CROSS_FILE_CASE_MISMATCH_SEVERITIES, warn);
894
+ if (normalized) {
895
+ mapped_diagnostics.case_mismatch = normalized;
896
+ }
897
+ }
898
+ }
899
+ maybe_assign_object(mapped, "diagnostics", mapped_diagnostics);
842
900
  return Object.keys(mapped).length > 0 ? mapped : undefined;
843
901
  }
844
902
  function map_public_config_to_partial_config(raw, warn) {
@@ -857,12 +915,21 @@ function map_public_config_to_partial_config(raw, warn) {
857
915
  warn_invalid_value("adoPaths", ado_paths, warn);
858
916
  }
859
917
  }
860
- const exclude = pick_key(raw, "exclude", warn, "exclude");
918
+ const workspace = object_value(raw, "workspace", "workspace", warn);
919
+ if (workspace) {
920
+ warn_unknown_keys(workspace, ["exclude"], "workspace", warn);
921
+ }
922
+ const canonical_exclude = workspace ? pick_key(workspace, "exclude", warn, "workspace.exclude") : undefined;
923
+ const legacy_exclude = pick_key(raw, "exclude", warn, "exclude");
924
+ const exclude = canonical_exclude !== undefined ? canonical_exclude : legacy_exclude;
925
+ if (canonical_exclude !== undefined && legacy_exclude !== undefined) {
926
+ warn_compat_collision("workspace.exclude", "exclude", warn);
927
+ }
861
928
  if (exclude !== undefined) {
862
929
  if (Array.isArray(exclude) && exclude.every((item) => typeof item === "string")) {
863
930
  mapped.exclude = exclude;
864
931
  } else {
865
- warn_invalid_value("exclude", exclude, warn);
932
+ warn_invalid_value(canonical_exclude !== undefined ? "workspace.exclude" : "exclude", exclude, warn);
866
933
  }
867
934
  }
868
935
  assign_string_member(root, "lineCommentStyle", raw, "lineCommentStyle", "lineCommentStyle", LINE_COMMENT_STYLES, warn);
@@ -941,6 +1008,7 @@ var init_schema = __esm(() => {
941
1008
  "indexWorkspace",
942
1009
  "adoPaths",
943
1010
  "exclude",
1011
+ "workspace",
944
1012
  "lineCommentStyle",
945
1013
  "debug",
946
1014
  "diagnostics",
@@ -12199,13 +12267,6 @@ class StataLexer {
12199
12267
  while (this.peek() === " " || this.peek() === "\t" || this.peek() === "\r") {
12200
12268
  this.advance();
12201
12269
  }
12202
- if (this.state.delimiterMode === "semicolon") {
12203
- return {
12204
- type: "WHITESPACE",
12205
- value: this.source.substring(this.position_to_offset(startLine, startColumn), this.position),
12206
- range: this.makeRange(startLine, startColumn, this.line, this.column)
12207
- };
12208
- }
12209
12270
  return null;
12210
12271
  }
12211
12272
  if (char === `
@@ -12214,17 +12275,11 @@ class StataLexer {
12214
12275
  return {
12215
12276
  type: "STATEMENT_TERMINATOR",
12216
12277
  value: `
12217
- `,
12218
- range: this.makeRange(startLine, startColumn, this.line, this.column)
12219
- };
12220
- } else {
12221
- return {
12222
- type: "WHITESPACE",
12223
- value: `
12224
12278
  `,
12225
12279
  range: this.makeRange(startLine, startColumn, this.line, this.column)
12226
12280
  };
12227
12281
  }
12282
+ return null;
12228
12283
  }
12229
12284
  if (char === ";") {
12230
12285
  if (this.state.delimiterMode === "semicolon") {
@@ -13205,6 +13260,34 @@ function is_swallowed_continuation_terminator(my_token, in_continuation) {
13205
13260
  }
13206
13261
 
13207
13262
  // src/parser/index.ts
13263
+ class ContinuationTracker {
13264
+ required_line = -1;
13265
+ prev_collected_line = null;
13266
+ prev_collected_end = null;
13267
+ chain_clean = true;
13268
+ note_continuation(continuation_token) {
13269
+ const cont_line = continuation_token.range.start.line;
13270
+ if (cont_line !== this.required_line) {
13271
+ return;
13272
+ }
13273
+ const cont_col = continuation_token.range.start.character;
13274
+ const is_first_on_prev_line = this.prev_collected_end !== null && this.required_line === this.prev_collected_line && cont_line === this.prev_collected_end.line;
13275
+ const has_leading_gap = is_first_on_prev_line ? cont_col > this.prev_collected_end.character : cont_col > 0;
13276
+ if (has_leading_gap) {
13277
+ this.chain_clean = false;
13278
+ }
13279
+ this.required_line += 1;
13280
+ }
13281
+ collect(token) {
13282
+ const token_line = token.range.start.line;
13283
+ const follows_continuation = this.prev_collected_line !== null && token_line > this.prev_collected_line && token_line === this.required_line && this.chain_clean;
13284
+ this.prev_collected_line = token_line;
13285
+ this.prev_collected_end = token.range.end;
13286
+ this.required_line = token_line;
13287
+ this.chain_clean = true;
13288
+ return follows_continuation;
13289
+ }
13290
+ }
13208
13291
  var PREFIX_COMMANDS, StataParser;
13209
13292
  var init_parser = __esm(() => {
13210
13293
  init_types();
@@ -13274,7 +13357,7 @@ var init_parser = __esm(() => {
13274
13357
  node = this.parseEmbeddedLanguageBlock("python");
13275
13358
  } else if (this.check("WORD") && this.isPrefixCommand(this.peek().value)) {
13276
13359
  node = this.parseCommand();
13277
- } else if (this.checkWord("program") && this.peekNext()?.value === "define") {
13360
+ } else if (this.checkWord("program") && this.tokens[this.current + 1]?.value === "define") {
13278
13361
  node = this.parseProgramDefinition();
13279
13362
  } else if (this.checkWord("syntax")) {
13280
13363
  node = this.parseSyntaxCommand();
@@ -13408,9 +13491,18 @@ var init_parser = __esm(() => {
13408
13491
  content += my_token.value;
13409
13492
  if (i < content_tokens.length - 1) {
13410
13493
  const next_token = content_tokens[i + 1];
13411
- const gap = next_token.range.start.character - my_token.range.end.character;
13412
- if (gap > 0) {
13413
- content += " ".repeat(gap);
13494
+ if (next_token.range.start.line === my_token.range.end.line) {
13495
+ const gap = next_token.range.start.character - my_token.range.end.character;
13496
+ if (gap > 0) {
13497
+ content += " ".repeat(gap);
13498
+ }
13499
+ } else if (next_token.range.start.line > my_token.range.end.line) {
13500
+ const line_gap = next_token.range.start.line - my_token.range.end.line;
13501
+ content += `
13502
+ `.repeat(line_gap);
13503
+ if (next_token.range.start.character > 0) {
13504
+ content += " ".repeat(next_token.range.start.character);
13505
+ }
13414
13506
  }
13415
13507
  }
13416
13508
  }
@@ -13449,12 +13541,20 @@ var init_parser = __esm(() => {
13449
13541
  const body = [];
13450
13542
  const was_inside_program = this.inside_program;
13451
13543
  this.inside_program = true;
13452
- while (!this.isAtEnd() && !this.checkWord("end")) {
13544
+ while (!this.isAtEnd()) {
13545
+ const my_trivia = this.collectTrivia();
13546
+ if (my_trivia.length > 0) {
13547
+ this.pending_trivia.push(...my_trivia);
13548
+ }
13549
+ if (this.checkWord("end") || this.isAtEnd()) {
13550
+ break;
13551
+ }
13453
13552
  const stmt = this.parseStatement();
13454
13553
  if (stmt) {
13455
13554
  body.push(stmt);
13456
13555
  }
13457
13556
  }
13557
+ const program_block_ending_trivia = this.checkWord("end") ? this.drainBlockEndingTrivia() : undefined;
13458
13558
  this.inside_program = was_inside_program;
13459
13559
  if (this.checkWord("end")) {
13460
13560
  this.advance();
@@ -13467,7 +13567,8 @@ var init_parser = __esm(() => {
13467
13567
  name: program_name,
13468
13568
  body,
13469
13569
  signature: merged_signature,
13470
- range: this.makeRange(start_token.range.start, this.previous().range.end)
13570
+ range: this.makeRange(start_token.range.start, this.previous().range.end),
13571
+ blockEndingTrivia: program_block_ending_trivia
13471
13572
  };
13472
13573
  }
13473
13574
  extract_and_merge_signatures(body) {
@@ -13538,20 +13639,20 @@ var init_parser = __esm(() => {
13538
13639
  let paren_depth = 0;
13539
13640
  const value_tokens = [];
13540
13641
  const preceded_by_continuation = [];
13541
- let continuation_pending = false;
13642
+ const continuation = new ContinuationTracker;
13542
13643
  while (!this.check("STATEMENT_TERMINATOR") && !this.isAtEnd()) {
13543
- if (this.skipContinuation()) {
13544
- continuation_pending = true;
13545
- continue;
13644
+ if (this.check("CONTINUATION")) {
13645
+ const cont_token = this.peek();
13646
+ if (this.skipContinuation()) {
13647
+ continuation.note_continuation(cont_token);
13648
+ continue;
13649
+ }
13546
13650
  }
13547
13651
  const token_type = this.peek().type;
13548
13652
  if (token_type === "COMMENT_LINE" || token_type === "COMMENT_BLOCK") {
13549
13653
  break;
13550
13654
  }
13551
13655
  const token = this.advance();
13552
- if (token.type === "WHITESPACE") {
13553
- continue;
13554
- }
13555
13656
  if (token.type === "LPAREN") {
13556
13657
  paren_depth++;
13557
13658
  } else if (token.type === "RPAREN") {
@@ -13562,8 +13663,7 @@ var init_parser = __esm(() => {
13562
13663
  }
13563
13664
  }
13564
13665
  value_tokens.push(token);
13565
- preceded_by_continuation.push(continuation_pending);
13566
- continuation_pending = false;
13666
+ preceded_by_continuation.push(continuation.collect(token));
13567
13667
  }
13568
13668
  const value = prefix_value + this.reconstruct_value_tokens(value_tokens, preceded_by_continuation);
13569
13669
  if (paren_depth > 0) {
@@ -13593,17 +13693,26 @@ var init_parser = __esm(() => {
13593
13693
  const function_name = this.advance().value;
13594
13694
  this.skipMacroDefinitionTrivia();
13595
13695
  const arg_tokens = [];
13696
+ const value_tokens = [];
13697
+ const preceded_by_continuation = [];
13698
+ const continuation = new ContinuationTracker;
13596
13699
  while (!this.check("STATEMENT_TERMINATOR") && !this.isAtEnd()) {
13597
- if (this.skipContinuation()) {
13598
- continue;
13700
+ if (this.check("CONTINUATION")) {
13701
+ const cont_token = this.peek();
13702
+ if (this.skipContinuation()) {
13703
+ continuation.note_continuation(cont_token);
13704
+ continue;
13705
+ }
13599
13706
  }
13600
13707
  if (this.check("COMMENT_LINE") || this.check("COMMENT_BLOCK")) {
13601
13708
  break;
13602
13709
  }
13603
13710
  const token = this.advance();
13604
13711
  arg_tokens.push(token);
13712
+ value_tokens.push(token);
13713
+ preceded_by_continuation.push(continuation.collect(token));
13605
13714
  }
13606
- const args_trimmed = this.reconstructTokensWithSpacing(arg_tokens).trim();
13715
+ const args_trimmed = this.reconstruct_value_tokens(value_tokens, preceded_by_continuation).trim();
13607
13716
  const extended_function = {
13608
13717
  name: function_name,
13609
13718
  args: args_trimmed,
@@ -13742,12 +13851,8 @@ var init_parser = __esm(() => {
13742
13851
  const has_condition_before = lbrace_token.range.start.line === command_start_line;
13743
13852
  this.validate_open_brace(lbrace_token, lbrace_index, has_condition_before);
13744
13853
  const body = [];
13745
- while (!this.check("RBRACE") && !this.isAtEnd()) {
13746
- const stmt = this.parseStatement();
13747
- if (stmt) {
13748
- body.push(stmt);
13749
- }
13750
- }
13854
+ const { body: my_body, blockEndingTrivia } = this.parseBraceBody();
13855
+ body.push(...my_body);
13751
13856
  if (this.check("RBRACE")) {
13752
13857
  const rbrace_index = this.current;
13753
13858
  const rbrace_token = this.advance();
@@ -13760,7 +13865,8 @@ var init_parser = __esm(() => {
13760
13865
  fullName: "{",
13761
13866
  expression: undefined,
13762
13867
  body,
13763
- range: this.makeRange(start_token.range.start, this.previous().range.end)
13868
+ range: this.makeRange(start_token.range.start, this.previous().range.end),
13869
+ blockEndingTrivia
13764
13870
  };
13765
13871
  }
13766
13872
  if (!this.check("WORD") && !this.check("MACRO_REF_LOCAL") && !this.check("MACRO_REF_GLOBAL")) {
@@ -13782,24 +13888,25 @@ var init_parser = __esm(() => {
13782
13888
  if (commandName === "args") {
13783
13889
  return this.parseArgsCommand(command_token, prefixes);
13784
13890
  }
13785
- if (commandName === "frame" && this.check("WORD")) {
13891
+ if (commandName === "frame") {
13786
13892
  const saved_pos = this.current;
13787
- const frame_name_token = this.advance();
13788
- this.skipTrivia();
13789
- if (this.check("COLON")) {
13790
- this.advance();
13791
- const frame_prefix = {
13792
- type: "prefix",
13793
- name: "frame",
13794
- fullName: "frame",
13795
- frameName: frame_name_token.value,
13796
- has_colon: true,
13797
- range: this.makeRange(command_token.range.start, this.previous().range.end)
13798
- };
13799
- return this.parseFramePrefixedCommand(frame_prefix, prefixes, start_token);
13800
- } else {
13801
- this.current = saved_pos;
13893
+ if (this.check("WORD")) {
13894
+ const frame_name_token = this.advance();
13895
+ this.skipTrivia();
13896
+ if (this.check("COLON")) {
13897
+ this.advance();
13898
+ const frame_prefix = {
13899
+ type: "prefix",
13900
+ name: "frame",
13901
+ fullName: "frame",
13902
+ frameName: frame_name_token.value,
13903
+ has_colon: true,
13904
+ range: this.makeRange(command_token.range.start, this.previous().range.end)
13905
+ };
13906
+ return this.parseFramePrefixedCommand(frame_prefix, prefixes, start_token);
13907
+ }
13802
13908
  }
13909
+ this.current = saved_pos;
13803
13910
  }
13804
13911
  return this.parseCommandBody(command_token, prefixes, start_token);
13805
13912
  }
@@ -13917,6 +14024,9 @@ var init_parser = __esm(() => {
13917
14024
  const parsed = this.parse_option_argument_inside_parens();
13918
14025
  option.argument = parsed.argument;
13919
14026
  option.argument_range = parsed.argument_range;
14027
+ if (parsed.argument_unclosed) {
14028
+ option.argument_unclosed = true;
14029
+ }
13920
14030
  }
13921
14031
  options.push(option);
13922
14032
  } else {
@@ -13939,19 +14049,41 @@ var init_parser = __esm(() => {
13939
14049
  }
13940
14050
  parse_option_argument_inside_parens() {
13941
14051
  this.advance();
13942
- let argument = "";
13943
14052
  const arg_tokens = [];
13944
- while (!this.check("RPAREN") && !this.isAtEnd()) {
14053
+ const preceded_by_continuation = [];
14054
+ const continuation = new ContinuationTracker;
14055
+ let paren_depth = 1;
14056
+ while (!this.isAtEnd() && paren_depth > 0) {
14057
+ if (this.check("STATEMENT_TERMINATOR")) {
14058
+ break;
14059
+ }
14060
+ if (this.check("CONTINUATION")) {
14061
+ const cont_token = this.peek();
14062
+ if (this.skipContinuation()) {
14063
+ continuation.note_continuation(cont_token);
14064
+ continue;
14065
+ }
14066
+ }
14067
+ if (this.check("COMMENT_LINE") || this.check("COMMENT_BLOCK")) {
14068
+ this.advance();
14069
+ continue;
14070
+ }
13945
14071
  const t = this.advance();
14072
+ if (t.type === "LPAREN") {
14073
+ paren_depth++;
14074
+ } else if (t.type === "RPAREN") {
14075
+ paren_depth--;
14076
+ if (paren_depth === 0) {
14077
+ break;
14078
+ }
14079
+ }
13946
14080
  arg_tokens.push(t);
13947
- argument += t.value;
14081
+ preceded_by_continuation.push(continuation.collect(t));
13948
14082
  }
13949
- if (this.check("RPAREN")) {
13950
- this.advance();
13951
- }
13952
- const non_ws = arg_tokens.filter((t) => t.type !== "WHITESPACE");
13953
- const argument_range = non_ws.length > 0 ? { start: non_ws[0].range.start, end: non_ws[non_ws.length - 1].range.end } : undefined;
13954
- return { argument, argument_range };
14083
+ const argument_unclosed = paren_depth > 0 ? true : undefined;
14084
+ const argument = this.reconstruct_value_tokens(arg_tokens, preceded_by_continuation);
14085
+ const argument_range = arg_tokens.length > 0 ? { start: arg_tokens[0].range.start, end: arg_tokens[arg_tokens.length - 1].range.end } : undefined;
14086
+ return { argument, argument_range, argument_unclosed };
13955
14087
  }
13956
14088
  parseFilePathArgument() {
13957
14089
  if (this.check("STRING")) {
@@ -13967,13 +14099,18 @@ var init_parser = __esm(() => {
13967
14099
  const start_token = this.advance();
13968
14100
  let path3 = start_token.value;
13969
14101
  let end_range = start_token.range.end;
14102
+ let prev_token = start_token;
13970
14103
  while (!this.isAtEnd()) {
13971
- if (this.check("WHITESPACE") || this.check("COMMA") || this.check("STATEMENT_TERMINATOR") || this.isTrivia()) {
14104
+ if (this.check("COMMA") || this.check("STATEMENT_TERMINATOR") || this.isTrivia()) {
14105
+ break;
14106
+ }
14107
+ if (!this.isAdjacentToken(prev_token, this.peek())) {
13972
14108
  break;
13973
14109
  }
13974
14110
  const token = this.advance();
13975
14111
  path3 += token.value;
13976
14112
  end_range = token.range.end;
14113
+ prev_token = token;
13977
14114
  }
13978
14115
  return {
13979
14116
  name: path3,
@@ -13982,39 +14119,56 @@ var init_parser = __esm(() => {
13982
14119
  }
13983
14120
  parseParenthesizedGroup() {
13984
14121
  const paren_start = this.advance();
13985
- const paren_parts = [];
14122
+ const inner_tokens = [];
14123
+ const preceded_by_continuation = [];
14124
+ const continuation = new ContinuationTracker;
13986
14125
  let paren_depth = 1;
13987
- let last_was_word = false;
14126
+ const collect = (my_token) => {
14127
+ inner_tokens.push(my_token);
14128
+ preceded_by_continuation.push(continuation.collect(my_token));
14129
+ };
13988
14130
  while (!this.isAtEnd() && paren_depth > 0) {
14131
+ if (this.check("STATEMENT_TERMINATOR")) {
14132
+ break;
14133
+ }
14134
+ if (this.check("CONTINUATION")) {
14135
+ const cont_token = this.peek();
14136
+ if (this.skipContinuation()) {
14137
+ continuation.note_continuation(cont_token);
14138
+ continue;
14139
+ }
14140
+ }
13989
14141
  if (this.check("LPAREN")) {
13990
14142
  paren_depth++;
13991
- paren_parts.push(this.advance().value);
13992
- last_was_word = false;
14143
+ collect(this.advance());
13993
14144
  } else if (this.check("RPAREN")) {
13994
14145
  paren_depth--;
13995
14146
  if (paren_depth > 0) {
13996
- paren_parts.push(this.advance().value);
14147
+ collect(this.advance());
13997
14148
  }
13998
- last_was_word = false;
13999
14149
  } else {
14000
- const current_is_word = this.check("WORD") || this.check("NUMBER") || this.check("MACRO_REF_LOCAL") || this.check("MACRO_REF_GLOBAL");
14001
- if (last_was_word && current_is_word) {
14002
- paren_parts.push(" ");
14003
- }
14004
- paren_parts.push(this.advance().value);
14005
- last_was_word = current_is_word;
14150
+ collect(this.advance());
14006
14151
  }
14007
14152
  }
14008
- const paren_content = paren_parts.join("");
14009
- const paren_end_pos = this.check("RPAREN") ? this.peek().range.end : this.previous().range.end;
14010
- if (this.check("RPAREN")) {
14153
+ const paren_content = this.reconstruct_value_tokens(inner_tokens, preceded_by_continuation);
14154
+ const closed_group = paren_depth === 0 && this.check("RPAREN");
14155
+ const paren_end_pos = closed_group ? this.peek().range.end : this.previous().range.end;
14156
+ if (closed_group) {
14011
14157
  this.advance();
14012
14158
  }
14013
14159
  if (!paren_content.trim()) {
14014
- return null;
14160
+ if (closed_group) {
14161
+ return null;
14162
+ }
14163
+ return {
14164
+ name: "(",
14165
+ recovery_only: true,
14166
+ range: this.makeRange(paren_start.range.start, paren_end_pos)
14167
+ };
14015
14168
  }
14169
+ const group_name = closed_group ? `(${paren_content})` : `(${paren_content}`;
14016
14170
  return {
14017
- name: `(${paren_content})`,
14171
+ name: group_name,
14018
14172
  range: this.makeRange(paren_start.range.start, paren_end_pos)
14019
14173
  };
14020
14174
  }
@@ -14071,6 +14225,9 @@ var init_parser = __esm(() => {
14071
14225
  const parsed = this.parse_option_argument_inside_parens();
14072
14226
  option.argument = parsed.argument;
14073
14227
  option.argument_range = parsed.argument_range;
14228
+ if (parsed.argument_unclosed) {
14229
+ option.argument_unclosed = true;
14230
+ }
14074
14231
  }
14075
14232
  options.push(option);
14076
14233
  } else {
@@ -14123,8 +14280,22 @@ var init_parser = __esm(() => {
14123
14280
  let allows_arbitrary_options = false;
14124
14281
  const seen_option_names = new Set;
14125
14282
  const syntax_tokens = [];
14126
- while (!this.check("STATEMENT_TERMINATOR") && !this.isAtEnd() && !this.isTrivia()) {
14127
- syntax_tokens.push(this.advance());
14283
+ const preceded_by_continuation = [];
14284
+ const continuation = new ContinuationTracker;
14285
+ while (!this.check("STATEMENT_TERMINATOR") && !this.isAtEnd()) {
14286
+ if (this.check("CONTINUATION")) {
14287
+ const cont_token = this.peek();
14288
+ if (this.skipContinuation()) {
14289
+ continuation.note_continuation(cont_token);
14290
+ continue;
14291
+ }
14292
+ }
14293
+ if (this.check("COMMENT_LINE") || this.check("COMMENT_BLOCK")) {
14294
+ break;
14295
+ }
14296
+ const my_token = this.advance();
14297
+ syntax_tokens.push(my_token);
14298
+ preceded_by_continuation.push(continuation.collect(my_token));
14128
14299
  }
14129
14300
  let token_idx = 0;
14130
14301
  while (token_idx < syntax_tokens.length) {
@@ -14145,7 +14316,7 @@ var init_parser = __esm(() => {
14145
14316
  const my_token = syntax_tokens[token_idx];
14146
14317
  if (my_token.type === "OPERATOR" && my_token.value === "*") {
14147
14318
  if (token_idx + 1 < syntax_tokens.length && syntax_tokens[token_idx + 1].type === "WORD") {
14148
- const opt_result2 = this.parse_option_spec(syntax_tokens, token_idx);
14319
+ const opt_result2 = this.parse_option_spec(syntax_tokens, token_idx, preceded_by_continuation);
14149
14320
  if (opt_result2) {
14150
14321
  const opt_name_lower = opt_result2.spec.name;
14151
14322
  seen_option_names.add(opt_name_lower);
@@ -14160,7 +14331,7 @@ var init_parser = __esm(() => {
14160
14331
  }
14161
14332
  continue;
14162
14333
  }
14163
- const opt_result = this.parse_option_spec(syntax_tokens, token_idx);
14334
+ const opt_result = this.parse_option_spec(syntax_tokens, token_idx, preceded_by_continuation);
14164
14335
  if (opt_result) {
14165
14336
  const opt_name_lower = opt_result.spec.name;
14166
14337
  seen_option_names.add(opt_name_lower);
@@ -14269,7 +14440,7 @@ var init_parser = __esm(() => {
14269
14440
  };
14270
14441
  return { spec, next_idx: current_idx };
14271
14442
  }
14272
- parse_option_spec(tokens, start_idx) {
14443
+ parse_option_spec(tokens, start_idx, preceded_by_continuation = []) {
14273
14444
  if (start_idx >= tokens.length) {
14274
14445
  return null;
14275
14446
  }
@@ -14302,6 +14473,7 @@ var init_parser = __esm(() => {
14302
14473
  if (current_idx < tokens.length && tokens[current_idx].type === "LPAREN") {
14303
14474
  current_idx++;
14304
14475
  const type_tokens = [];
14476
+ const type_flags = [];
14305
14477
  let paren_depth = 1;
14306
14478
  while (current_idx < tokens.length && paren_depth > 0) {
14307
14479
  const my_token = tokens[current_idx];
@@ -14314,6 +14486,7 @@ var init_parser = __esm(() => {
14314
14486
  }
14315
14487
  }
14316
14488
  type_tokens.push(my_token);
14489
+ type_flags.push(preceded_by_continuation[current_idx] ?? false);
14317
14490
  current_idx++;
14318
14491
  }
14319
14492
  if (current_idx < tokens.length && tokens[current_idx].type === "RPAREN") {
@@ -14336,25 +14509,25 @@ var init_parser = __esm(() => {
14336
14509
  argument_type = first_part;
14337
14510
  for (let i = 1;i < type_tokens.length; i++) {
14338
14511
  if (type_tokens[i].type === "WORD" && type_tokens[i].value === "default" && i + 1 < type_tokens.length && type_tokens[i + 1].type === "LPAREN") {
14339
- let default_str = "";
14512
+ const default_tokens = [];
14513
+ const default_flags = [];
14340
14514
  let default_paren_depth = 1;
14341
14515
  let j = i + 2;
14342
14516
  while (j < type_tokens.length && default_paren_depth > 0) {
14343
14517
  const my_token = type_tokens[j];
14344
14518
  if (my_token.type === "LPAREN") {
14345
14519
  default_paren_depth++;
14346
- default_str += my_token.value;
14347
14520
  } else if (my_token.type === "RPAREN") {
14348
14521
  default_paren_depth--;
14349
- if (default_paren_depth > 0) {
14350
- default_str += my_token.value;
14522
+ if (default_paren_depth === 0) {
14523
+ break;
14351
14524
  }
14352
- } else {
14353
- default_str += my_token.value;
14354
14525
  }
14526
+ default_tokens.push(my_token);
14527
+ default_flags.push(type_flags[j] ?? false);
14355
14528
  j++;
14356
14529
  }
14357
- default_value = default_str.trim();
14530
+ default_value = this.reconstruct_value_tokens(default_tokens, default_flags).trim();
14358
14531
  break;
14359
14532
  }
14360
14533
  }
@@ -14455,11 +14628,17 @@ var init_parser = __esm(() => {
14455
14628
  parseIfStatement() {
14456
14629
  const ifToken = this.advance();
14457
14630
  const condition_tokens = [];
14631
+ const preceded_by_continuation = [];
14632
+ const continuation = new ContinuationTracker;
14458
14633
  const condition_start_line = ifToken.range.start.line;
14459
14634
  let paren_depth = 0;
14460
14635
  while (!this.check("LBRACE") && !this.isAtEnd()) {
14461
- if (this.skipContinuation()) {
14462
- continue;
14636
+ if (this.check("CONTINUATION")) {
14637
+ const cont_token = this.peek();
14638
+ if (this.skipContinuation()) {
14639
+ continuation.note_continuation(cont_token);
14640
+ continue;
14641
+ }
14463
14642
  }
14464
14643
  if (this.check("STATEMENT_TERMINATOR")) {
14465
14644
  break;
@@ -14474,11 +14653,10 @@ var init_parser = __esm(() => {
14474
14653
  paren_depth = 0;
14475
14654
  }
14476
14655
  }
14477
- if (token.type !== "WHITESPACE") {
14478
- condition_tokens.push(token);
14479
- }
14656
+ condition_tokens.push(token);
14657
+ preceded_by_continuation.push(continuation.collect(token));
14480
14658
  }
14481
- const condition = this.reconstructTokensWithSpacing(condition_tokens).trim();
14659
+ const condition = this.reconstruct_value_tokens(condition_tokens, preceded_by_continuation).trim();
14482
14660
  if (paren_depth > 0) {
14483
14661
  this.addError("Unbalanced parentheses in if condition: missing closing parenthesis", ifToken.range, "UNBALANCED_PARENTHESES" /* UNBALANCED_PARENTHESES */);
14484
14662
  }
@@ -14486,17 +14664,15 @@ var init_parser = __esm(() => {
14486
14664
  this.addError("Empty if condition", ifToken.range, "MISSING_EXPRESSION_AFTER_EQUALS" /* MISSING_EXPRESSION_AFTER_EQUALS */);
14487
14665
  }
14488
14666
  const body = [];
14667
+ let blockEndingTrivia;
14489
14668
  if (this.check("LBRACE")) {
14490
14669
  const lbrace_index = this.current;
14491
14670
  const lbrace_token = this.advance();
14492
14671
  const has_condition_before = lbrace_token.range.start.line === condition_start_line;
14493
14672
  this.validate_open_brace(lbrace_token, lbrace_index, has_condition_before);
14494
- while (!this.check("RBRACE") && !this.isAtEnd()) {
14495
- const stmt = this.parseStatement();
14496
- if (stmt) {
14497
- body.push(stmt);
14498
- }
14499
- }
14673
+ const { body: my_body, blockEndingTrivia: my_block_ending_trivia } = this.parseBraceBody();
14674
+ body.push(...my_body);
14675
+ blockEndingTrivia = my_block_ending_trivia;
14500
14676
  if (this.check("RBRACE")) {
14501
14677
  const rbrace_index = this.current;
14502
14678
  const rbrace_token = this.advance();
@@ -14509,7 +14685,8 @@ var init_parser = __esm(() => {
14509
14685
  type: "if",
14510
14686
  condition: condition.trim(),
14511
14687
  body,
14512
- range: this.makeRange(ifToken.range.start, this.previous().range.end)
14688
+ range: this.makeRange(ifToken.range.start, this.previous().range.end),
14689
+ blockEndingTrivia
14513
14690
  };
14514
14691
  }
14515
14692
  parseElseStatement() {
@@ -14523,17 +14700,17 @@ var init_parser = __esm(() => {
14523
14700
  };
14524
14701
  }
14525
14702
  const body = [];
14703
+ let blockEndingTrivia;
14704
+ let is_brace_else = false;
14526
14705
  if (this.check("LBRACE")) {
14706
+ is_brace_else = true;
14527
14707
  const lbrace_index = this.current;
14528
14708
  const lbrace_token = this.advance();
14529
14709
  const has_condition_before = lbrace_token.range.start.line === elseToken.range.start.line;
14530
14710
  this.validate_open_brace(lbrace_token, lbrace_index, has_condition_before);
14531
- while (!this.check("RBRACE") && !this.isAtEnd()) {
14532
- const stmt = this.parseStatement();
14533
- if (stmt) {
14534
- body.push(stmt);
14535
- }
14536
- }
14711
+ const { body: my_body, blockEndingTrivia: my_block_ending_trivia } = this.parseBraceBody();
14712
+ body.push(...my_body);
14713
+ blockEndingTrivia = my_block_ending_trivia;
14537
14714
  if (this.check("RBRACE")) {
14538
14715
  const rbrace_index = this.current;
14539
14716
  const rbrace_token = this.advance();
@@ -14547,10 +14724,12 @@ var init_parser = __esm(() => {
14547
14724
  body.push(stmt);
14548
14725
  }
14549
14726
  }
14727
+ const end_position = !is_brace_else && body.length > 0 ? body[body.length - 1].range.end : this.previous().range.end;
14550
14728
  return {
14551
14729
  type: "else",
14552
14730
  body,
14553
- range: this.makeRange(elseToken.range.start, this.previous().range.end)
14731
+ range: this.makeRange(elseToken.range.start, end_position),
14732
+ blockEndingTrivia
14554
14733
  };
14555
14734
  }
14556
14735
  parseLoopStatement() {
@@ -14566,9 +14745,9 @@ var init_parser = __esm(() => {
14566
14745
  const is_foreach_spec = this.check("WORD") && (this.peek().value === "in" || this.peek().value === "of");
14567
14746
  if (is_forvalues_spec || is_foreach_spec) {
14568
14747
  const specType = this.advance().value;
14569
- let loop_spec_body = "";
14570
- let prev_spec_token = null;
14571
- let continuation_pending = false;
14748
+ const spec_tokens = [];
14749
+ const preceded_by_continuation = [];
14750
+ const continuation = new ContinuationTracker;
14572
14751
  while (!this.check("LBRACE") && !this.isAtEnd()) {
14573
14752
  if (this.check("STATEMENT_TERMINATOR")) {
14574
14753
  if (is_swallowed_continuation_terminator(this.tokens[this.current], this.current > 0 && this.tokens[this.current - 1].type === "CONTINUATION")) {
@@ -14579,44 +14758,24 @@ var init_parser = __esm(() => {
14579
14758
  }
14580
14759
  const token = this.advance();
14581
14760
  if (token.type === "CONTINUATION") {
14582
- continuation_pending = true;
14583
- continue;
14584
- }
14585
- if (token.type === "WHITESPACE") {
14761
+ continuation.note_continuation(token);
14586
14762
  continue;
14587
14763
  }
14588
- if (prev_spec_token !== null) {
14589
- const same_line = prev_spec_token.range.end.line === token.range.start.line;
14590
- let needs_separator;
14591
- if (same_line) {
14592
- needs_separator = token.range.start.character - prev_spec_token.range.end.character > 0;
14593
- } else if (continuation_pending) {
14594
- needs_separator = token.range.start.character > 0;
14595
- } else {
14596
- needs_separator = true;
14597
- }
14598
- if (needs_separator) {
14599
- loop_spec_body += " ";
14600
- }
14601
- }
14602
- loop_spec_body += token.value;
14603
- prev_spec_token = token;
14604
- continuation_pending = false;
14764
+ spec_tokens.push(token);
14765
+ preceded_by_continuation.push(continuation.collect(token));
14605
14766
  }
14606
- loopSpec = specType + " " + loop_spec_body.trim();
14767
+ loopSpec = specType + " " + this.reconstruct_value_tokens(spec_tokens, preceded_by_continuation).trim();
14607
14768
  }
14608
14769
  const body = [];
14770
+ let blockEndingTrivia;
14609
14771
  if (this.check("LBRACE")) {
14610
14772
  const lbrace_index = this.current;
14611
14773
  const lbrace_token = this.advance();
14612
14774
  const has_condition_before = lbrace_token.range.start.line === loop_start_line;
14613
14775
  this.validate_open_brace(lbrace_token, lbrace_index, has_condition_before);
14614
- while (!this.check("RBRACE") && !this.isAtEnd()) {
14615
- const stmt = this.parseStatement();
14616
- if (stmt) {
14617
- body.push(stmt);
14618
- }
14619
- }
14776
+ const { body: my_body, blockEndingTrivia: my_block_ending_trivia } = this.parseBraceBody();
14777
+ body.push(...my_body);
14778
+ blockEndingTrivia = my_block_ending_trivia;
14620
14779
  if (this.check("RBRACE")) {
14621
14780
  const rbrace_index = this.current;
14622
14781
  const rbrace_token = this.advance();
@@ -14630,17 +14789,24 @@ var init_parser = __esm(() => {
14630
14789
  loopVar,
14631
14790
  loopSpec,
14632
14791
  body,
14633
- range: this.makeRange(loopToken.range.start, this.previous().range.end)
14792
+ range: this.makeRange(loopToken.range.start, this.previous().range.end),
14793
+ blockEndingTrivia
14634
14794
  };
14635
14795
  }
14636
14796
  parseWhileStatement() {
14637
14797
  const whileToken = this.advance();
14638
14798
  const while_start_line = whileToken.range.start.line;
14639
14799
  const condition_tokens = [];
14800
+ const preceded_by_continuation = [];
14801
+ const continuation = new ContinuationTracker;
14640
14802
  let paren_depth = 0;
14641
14803
  while (!this.check("LBRACE") && !this.isAtEnd()) {
14642
- if (this.skipContinuation()) {
14643
- continue;
14804
+ if (this.check("CONTINUATION")) {
14805
+ const cont_token = this.peek();
14806
+ if (this.skipContinuation()) {
14807
+ continuation.note_continuation(cont_token);
14808
+ continue;
14809
+ }
14644
14810
  }
14645
14811
  if (this.check("STATEMENT_TERMINATOR")) {
14646
14812
  break;
@@ -14655,11 +14821,10 @@ var init_parser = __esm(() => {
14655
14821
  paren_depth = 0;
14656
14822
  }
14657
14823
  }
14658
- if (token.type !== "WHITESPACE") {
14659
- condition_tokens.push(token);
14660
- }
14824
+ condition_tokens.push(token);
14825
+ preceded_by_continuation.push(continuation.collect(token));
14661
14826
  }
14662
- const condition = this.reconstructTokensWithSpacing(condition_tokens).trim();
14827
+ const condition = this.reconstruct_value_tokens(condition_tokens, preceded_by_continuation).trim();
14663
14828
  if (paren_depth > 0) {
14664
14829
  this.addError("Unbalanced parentheses in while condition: missing closing parenthesis", whileToken.range, "UNBALANCED_PARENTHESES" /* UNBALANCED_PARENTHESES */);
14665
14830
  }
@@ -14667,17 +14832,15 @@ var init_parser = __esm(() => {
14667
14832
  this.addError("Empty while condition", whileToken.range, "MISSING_EXPRESSION_AFTER_EQUALS" /* MISSING_EXPRESSION_AFTER_EQUALS */);
14668
14833
  }
14669
14834
  const body = [];
14835
+ let blockEndingTrivia;
14670
14836
  if (this.check("LBRACE")) {
14671
14837
  const lbrace_index = this.current;
14672
14838
  const lbrace_token = this.advance();
14673
14839
  const has_condition_before = lbrace_token.range.start.line === while_start_line;
14674
14840
  this.validate_open_brace(lbrace_token, lbrace_index, has_condition_before);
14675
- while (!this.check("RBRACE") && !this.isAtEnd()) {
14676
- const stmt = this.parseStatement();
14677
- if (stmt) {
14678
- body.push(stmt);
14679
- }
14680
- }
14841
+ const { body: my_body, blockEndingTrivia: my_block_ending_trivia } = this.parseBraceBody();
14842
+ body.push(...my_body);
14843
+ blockEndingTrivia = my_block_ending_trivia;
14681
14844
  if (this.check("RBRACE")) {
14682
14845
  const rbrace_index = this.current;
14683
14846
  const rbrace_token = this.advance();
@@ -14690,7 +14853,8 @@ var init_parser = __esm(() => {
14690
14853
  type: "while",
14691
14854
  condition: condition.trim(),
14692
14855
  body,
14693
- range: this.makeRange(whileToken.range.start, this.previous().range.end)
14856
+ range: this.makeRange(whileToken.range.start, this.previous().range.end),
14857
+ blockEndingTrivia
14694
14858
  };
14695
14859
  }
14696
14860
  parseStringStatement() {
@@ -14698,9 +14862,7 @@ var init_parser = __esm(() => {
14698
14862
  const statement_tokens = [];
14699
14863
  while (!this.check("STATEMENT_TERMINATOR") && !this.isAtEnd()) {
14700
14864
  const token = this.advance();
14701
- if (token.type !== "WHITESPACE") {
14702
- statement_tokens.push(token);
14703
- }
14865
+ statement_tokens.push(token);
14704
14866
  }
14705
14867
  const content = this.reconstructTokensWithSpacing(statement_tokens);
14706
14868
  return {
@@ -14711,15 +14873,15 @@ var init_parser = __esm(() => {
14711
14873
  };
14712
14874
  }
14713
14875
  parseFrameBlock() {
14876
+ const frame_index = this.current;
14714
14877
  const frame_token = this.advance();
14715
14878
  const frame_start_line = frame_token.range.start.line;
14716
14879
  this.skipTrivia();
14717
14880
  if (!this.check("WORD")) {
14718
- this.current--;
14881
+ this.current = frame_index;
14719
14882
  return null;
14720
14883
  }
14721
14884
  const name_token = this.peek();
14722
- const saved_position = this.current;
14723
14885
  this.advance();
14724
14886
  this.skipTrivia();
14725
14887
  if (this.check("COLON")) {
@@ -14735,7 +14897,7 @@ var init_parser = __esm(() => {
14735
14897
  return this.parseFramePrefixedCommand(frame_prefix, [], frame_token);
14736
14898
  }
14737
14899
  if (!this.check("LBRACE")) {
14738
- this.current = saved_position - 1;
14900
+ this.current = frame_index;
14739
14901
  return null;
14740
14902
  }
14741
14903
  const frame_name = name_token.value;
@@ -14743,13 +14905,7 @@ var init_parser = __esm(() => {
14743
14905
  const lbrace_token = this.advance();
14744
14906
  const has_condition_before = lbrace_token.range.start.line === frame_start_line;
14745
14907
  this.validate_open_brace(lbrace_token, lbrace_index, has_condition_before);
14746
- const body = [];
14747
- while (!this.check("RBRACE") && !this.isAtEnd()) {
14748
- const stmt = this.parseStatement();
14749
- if (stmt) {
14750
- body.push(stmt);
14751
- }
14752
- }
14908
+ const { body, blockEndingTrivia } = this.parseBraceBody();
14753
14909
  if (this.check("RBRACE")) {
14754
14910
  const rbrace_index = this.current;
14755
14911
  const rbrace_token = this.advance();
@@ -14761,7 +14917,8 @@ var init_parser = __esm(() => {
14761
14917
  type: "frame",
14762
14918
  frameName: frame_name,
14763
14919
  body,
14764
- range: this.makeRange(frame_token.range.start, this.previous().range.end)
14920
+ range: this.makeRange(frame_token.range.start, this.previous().range.end),
14921
+ blockEndingTrivia
14765
14922
  };
14766
14923
  }
14767
14924
  parseUnabCommandBody(command_token) {
@@ -14822,6 +14979,9 @@ var init_parser = __esm(() => {
14822
14979
  const parsed = this.parse_option_argument_inside_parens();
14823
14980
  option.argument = parsed.argument;
14824
14981
  option.argument_range = parsed.argument_range;
14982
+ if (parsed.argument_unclosed) {
14983
+ option.argument_unclosed = true;
14984
+ }
14825
14985
  }
14826
14986
  options.push(option);
14827
14987
  } else {
@@ -14848,7 +15008,7 @@ var init_parser = __esm(() => {
14848
15008
  }
14849
15009
  collectTrivia() {
14850
15010
  const trivia = [];
14851
- while (this.check("COMMENT_LINE") || this.check("COMMENT_BLOCK") || this.check("CONTINUATION") || this.check("WHITESPACE")) {
15011
+ while (this.check("COMMENT_LINE") || this.check("COMMENT_BLOCK") || this.check("CONTINUATION")) {
14852
15012
  const token = this.advance();
14853
15013
  if (token.type === "COMMENT_LINE") {
14854
15014
  const style = token.value.startsWith("*") ? "star" : "slash";
@@ -14881,12 +15041,42 @@ var init_parser = __esm(() => {
14881
15041
  this.advance();
14882
15042
  }
14883
15043
  }
15044
+ parseBraceBody() {
15045
+ const body = [];
15046
+ while (!this.isAtEnd()) {
15047
+ const my_trivia = this.collectTrivia();
15048
+ if (my_trivia.length > 0) {
15049
+ this.pending_trivia.push(...my_trivia);
15050
+ }
15051
+ if (this.check("RBRACE") || this.isAtEnd()) {
15052
+ break;
15053
+ }
15054
+ const stmt = this.parseStatement();
15055
+ if (stmt) {
15056
+ body.push(stmt);
15057
+ }
15058
+ }
15059
+ const blockEndingTrivia = this.check("RBRACE") ? this.drainBlockEndingTrivia() : undefined;
15060
+ return { body, blockEndingTrivia };
15061
+ }
15062
+ drainBlockEndingTrivia() {
15063
+ if (this.pending_trivia.length === 0) {
15064
+ return;
15065
+ }
15066
+ const has_continuation = this.pending_trivia.some((my_trivia) => my_trivia.type === "comment" && my_trivia.style === "continuation");
15067
+ if (has_continuation) {
15068
+ return;
15069
+ }
15070
+ const block_ending_trivia = this.pending_trivia;
15071
+ this.pending_trivia = [];
15072
+ return block_ending_trivia;
15073
+ }
14884
15074
  skipMacroDefinitionTrivia() {
14885
15075
  while (!this.isAtEnd()) {
14886
15076
  if (this.skipContinuation()) {
14887
15077
  continue;
14888
15078
  }
14889
- if (this.check("WHITESPACE") || this.check("COMMENT_LINE") || this.check("COMMENT_BLOCK")) {
15079
+ if (this.check("COMMENT_LINE") || this.check("COMMENT_BLOCK")) {
14890
15080
  this.advance();
14891
15081
  continue;
14892
15082
  }
@@ -14903,7 +15093,7 @@ var init_parser = __esm(() => {
14903
15093
  }
14904
15094
  continue;
14905
15095
  }
14906
- if (token.type === "COMMENT_LINE" || token.type === "COMMENT_BLOCK" || token.type === "WHITESPACE") {
15096
+ if (token.type === "COMMENT_LINE" || token.type === "COMMENT_BLOCK") {
14907
15097
  offset++;
14908
15098
  continue;
14909
15099
  }
@@ -14985,11 +15175,6 @@ var init_parser = __esm(() => {
14985
15175
  peek() {
14986
15176
  return this.tokens[this.current];
14987
15177
  }
14988
- peekNext() {
14989
- if (this.current + 1 >= this.tokens.length)
14990
- return;
14991
- return this.tokens[this.current + 1];
14992
- }
14993
15178
  previous() {
14994
15179
  return this.tokens[this.current - 1];
14995
15180
  }
@@ -15007,12 +15192,18 @@ var init_parser = __esm(() => {
15007
15192
  return this.context_tracker;
15008
15193
  }
15009
15194
  parseExpression() {
15010
- let expression = "";
15195
+ const the_tokens = [];
15196
+ const preceded_by_continuation = [];
15197
+ const continuation = new ContinuationTracker;
15011
15198
  let paren_depth = 0;
15012
15199
  const start_pos = this.current;
15013
15200
  while (!this.isAtEnd()) {
15014
15201
  const token = this.peek();
15202
+ if (token.type === "STATEMENT_TERMINATOR") {
15203
+ break;
15204
+ }
15015
15205
  if (this.skipContinuation()) {
15206
+ continuation.note_continuation(token);
15016
15207
  continue;
15017
15208
  }
15018
15209
  if (token.type === "LPAREN") {
@@ -15025,7 +15216,7 @@ var init_parser = __esm(() => {
15025
15216
  }
15026
15217
  }
15027
15218
  if (paren_depth === 0) {
15028
- if (token.type === "COMMA" || token.type === "STATEMENT_TERMINATOR") {
15219
+ if (token.type === "COMMA") {
15029
15220
  break;
15030
15221
  }
15031
15222
  if (token.type === "WORD" && (token.value === "if" || token.value === "in")) {
@@ -15035,13 +15226,11 @@ var init_parser = __esm(() => {
15035
15226
  if (token.type === "COMMENT_LINE" || token.type === "COMMENT_BLOCK") {
15036
15227
  break;
15037
15228
  }
15038
- const tokenValue = this.advance().value;
15039
- if (token.type === "WHITESPACE") {
15040
- expression += " ";
15041
- } else {
15042
- expression += tokenValue;
15043
- }
15229
+ this.advance();
15230
+ the_tokens.push(token);
15231
+ preceded_by_continuation.push(continuation.collect(token));
15044
15232
  }
15233
+ const expression = this.reconstruct_value_tokens(the_tokens, preceded_by_continuation);
15045
15234
  if (paren_depth > 0) {
15046
15235
  const end_pos = this.current > 0 ? this.previous().range : this.peek().range;
15047
15236
  this.addError("Unbalanced parentheses: missing closing parenthesis", end_pos, "UNBALANCED_PARENTHESES" /* UNBALANCED_PARENTHESES */);
@@ -15073,7 +15262,9 @@ var init_parser = __esm(() => {
15073
15262
  return false;
15074
15263
  }
15075
15264
  parseQualifierExpressionWithStrayDetection(qualifier_type, stop_at_in, check_empty) {
15076
- let expression = "";
15265
+ const the_tokens = [];
15266
+ const preceded_by_continuation = [];
15267
+ const continuation = new ContinuationTracker;
15077
15268
  let paren_depth = 0;
15078
15269
  let bracket_depth = 0;
15079
15270
  const start_token = this.peek();
@@ -15082,6 +15273,9 @@ var init_parser = __esm(() => {
15082
15273
  let in_string_context = false;
15083
15274
  while (!this.isAtEnd()) {
15084
15275
  const token = this.peek();
15276
+ if (token.type === "STATEMENT_TERMINATOR") {
15277
+ break;
15278
+ }
15085
15279
  if (token.type === "LPAREN") {
15086
15280
  paren_depth++;
15087
15281
  state_stack.push("INITIAL");
@@ -15112,10 +15306,11 @@ var init_parser = __esm(() => {
15112
15306
  }
15113
15307
  }
15114
15308
  if (this.skipContinuation()) {
15309
+ continuation.note_continuation(token);
15115
15310
  continue;
15116
15311
  }
15117
15312
  if (paren_depth === 0 && bracket_depth === 0) {
15118
- if (token.type === "STATEMENT_TERMINATOR" || token.type === "COMMA") {
15313
+ if (token.type === "COMMA") {
15119
15314
  break;
15120
15315
  }
15121
15316
  if (stop_at_in && token.type === "WORD" && token.value === "in") {
@@ -15138,7 +15333,7 @@ var init_parser = __esm(() => {
15138
15333
  this.addError(`Unexpected token '${token.value}' after comparison expression. Did you mean to use '&' or '|'?`, token.range, "STRAY_TOKEN_IN_CONDITION" /* STRAY_TOKEN_IN_CONDITION */);
15139
15334
  }
15140
15335
  }
15141
- if (token.type !== "WHITESPACE" && token.type !== "LPAREN" && token.type !== "RPAREN" && bracket_depth === 0) {
15336
+ if (token.type !== "LPAREN" && token.type !== "RPAREN" && bracket_depth === 0) {
15142
15337
  const current_state_for_transition = state_stack[state_stack.length - 1];
15143
15338
  if (token.type === "OPERATOR") {
15144
15339
  if (token.value === "=" && prev_token && prev_token.type === "OPERATOR" && prev_token.value === "~") {
@@ -15166,17 +15361,14 @@ var init_parser = __esm(() => {
15166
15361
  }
15167
15362
  prev_token = token;
15168
15363
  }
15169
- const tokenValue = this.advance().value;
15170
- if (token.type === "WHITESPACE") {
15171
- expression += " ";
15172
- } else {
15173
- expression += tokenValue;
15174
- }
15364
+ this.advance();
15365
+ the_tokens.push(token);
15366
+ preceded_by_continuation.push(continuation.collect(token));
15175
15367
  }
15176
15368
  if (paren_depth > 0) {
15177
15369
  this.addError(`Unbalanced parentheses in ${qualifier_type} qualifier: missing closing parenthesis`, start_token.range, "UNBALANCED_PARENTHESES" /* UNBALANCED_PARENTHESES */);
15178
15370
  }
15179
- const trimmed_expression = expression.trim();
15371
+ const trimmed_expression = this.reconstruct_value_tokens(the_tokens, preceded_by_continuation).trim();
15180
15372
  if (check_empty && trimmed_expression === "") {
15181
15373
  this.addError(`Empty ${qualifier_type} qualifier expression`, start_token.range, "MISSING_EXPRESSION_AFTER_EQUALS" /* MISSING_EXPRESSION_AFTER_EQUALS */);
15182
15374
  }
@@ -15219,7 +15411,6 @@ var init_parser = __esm(() => {
15219
15411
  }
15220
15412
  scan_code_on_same_line(start_pos, line) {
15221
15413
  const trivia_types = [
15222
- "WHITESPACE",
15223
15414
  "COMMENT_LINE",
15224
15415
  "COMMENT_BLOCK",
15225
15416
  "CONTINUATION"
@@ -15258,7 +15449,6 @@ var init_parser = __esm(() => {
15258
15449
  }
15259
15450
  find_code_before_on_same_line(end_pos, line) {
15260
15451
  const trivia_types = [
15261
- "WHITESPACE",
15262
15452
  "COMMENT_LINE",
15263
15453
  "COMMENT_BLOCK",
15264
15454
  "CONTINUATION"
@@ -15378,12 +15568,12 @@ var init_parser = __esm(() => {
15378
15568
  if (gap > 0) {
15379
15569
  the_parts.push(" ".repeat(gap));
15380
15570
  }
15381
- } else {
15382
- const spaces_from_line_start = my_token.range.start.character;
15383
- if (spaces_from_line_start > 0) {
15384
- the_parts.push(" ".repeat(spaces_from_line_start));
15385
- } else {
15386
- the_parts.push(" ");
15571
+ } else if (my_token.range.start.line > prev_token.range.end.line) {
15572
+ const line_gap = my_token.range.start.line - prev_token.range.end.line;
15573
+ the_parts.push(`
15574
+ `.repeat(line_gap));
15575
+ if (my_token.range.start.character > 0) {
15576
+ the_parts.push(" ".repeat(my_token.range.start.character));
15387
15577
  }
15388
15578
  }
15389
15579
  }
@@ -16551,10 +16741,6 @@ function parse_macro_def_head(statement_tokens) {
16551
16741
  const run = [];
16552
16742
  while (i < statement_tokens.length) {
16553
16743
  const my_tok = statement_tokens[i];
16554
- if (my_tok.type === "WHITESPACE") {
16555
- i++;
16556
- continue;
16557
- }
16558
16744
  if (!NAME_TOKEN_TYPES.has(my_tok.type))
16559
16745
  break;
16560
16746
  if (run.length > 0 && !tokens_adjacent(run[run.length - 1], my_tok))
@@ -17046,6 +17232,57 @@ var init_macro_creating_commands = __esm(() => {
17046
17232
  ];
17047
17233
  });
17048
17234
 
17235
+ // src/utils/token-utils.ts
17236
+ function position_before2(a, b) {
17237
+ return a.line < b.line || a.line === b.line && a.character < b.character;
17238
+ }
17239
+ function position_at_or_before(a, b) {
17240
+ return a.line < b.line || a.line === b.line && a.character <= b.character;
17241
+ }
17242
+ function find_last_token_starting_at_or_before(tokens, position) {
17243
+ let low = 0;
17244
+ let high = tokens.length - 1;
17245
+ let best = -1;
17246
+ while (low <= high) {
17247
+ const mid = low + high >>> 1;
17248
+ const start = tokens[mid].range.start;
17249
+ if (position_at_or_before(start, position)) {
17250
+ best = mid;
17251
+ low = mid + 1;
17252
+ } else {
17253
+ high = mid - 1;
17254
+ }
17255
+ }
17256
+ return best;
17257
+ }
17258
+ function find_last_token_starting_before(tokens, position) {
17259
+ let low = 0;
17260
+ let high = tokens.length - 1;
17261
+ let best = -1;
17262
+ while (low <= high) {
17263
+ const mid = low + high >>> 1;
17264
+ const start = tokens[mid].range.start;
17265
+ if (position_before2(start, position)) {
17266
+ best = mid;
17267
+ low = mid + 1;
17268
+ } else {
17269
+ high = mid - 1;
17270
+ }
17271
+ }
17272
+ return best;
17273
+ }
17274
+ function find_token_index_at_position(tokens, position) {
17275
+ const candidate = find_last_token_starting_at_or_before(tokens, position);
17276
+ if (candidate === -1) {
17277
+ return -1;
17278
+ }
17279
+ const end = tokens[candidate].range.end;
17280
+ if (position_before2(position, end)) {
17281
+ return candidate;
17282
+ }
17283
+ return -1;
17284
+ }
17285
+
17049
17286
  // src/utils/statement-span.ts
17050
17287
  function next_statement_line_span(tokens, from_index) {
17051
17288
  let my_first_index = from_index + 1;
@@ -17086,13 +17323,101 @@ function next_statement_line_span(tokens, from_index) {
17086
17323
  }
17087
17324
  return { start_line, end_line };
17088
17325
  }
17089
- var LEADING_TRIVIA_TYPES, BODY_OPENER_TYPES, CONTINUATION_NEUTRAL_TYPES;
17326
+ function inline_embedded_statement_end(tokens, opener_index) {
17327
+ let my_last_content_index = opener_index;
17328
+ let my_i = opener_index + 1;
17329
+ while (my_i < tokens.length && tokens[my_i].type !== "STATEMENT_TERMINATOR" && tokens[my_i].type !== "EOF") {
17330
+ my_last_content_index = my_i;
17331
+ my_i++;
17332
+ }
17333
+ const end_line = tokens[my_last_content_index].range.end.line;
17334
+ const end_index = my_i < tokens.length && tokens[my_i].type === "STATEMENT_TERMINATOR" ? my_i : my_last_content_index;
17335
+ return { end_index, end_line };
17336
+ }
17337
+ function logical_statement_start(tokens, position, min_line = 0) {
17338
+ if (tokens === undefined || tokens.length === 0) {
17339
+ return;
17340
+ }
17341
+ let search_index = find_last_token_starting_before(tokens, position);
17342
+ while (search_index >= 0 && tokens[search_index].type === "EOF") {
17343
+ search_index--;
17344
+ }
17345
+ if (search_index < 0) {
17346
+ return;
17347
+ }
17348
+ let boundary_index = -1;
17349
+ let my_steps = 0;
17350
+ for (let my_i = search_index;my_i >= 0; my_i--) {
17351
+ if (++my_steps > MAX_STATEMENT_TOKENS) {
17352
+ return;
17353
+ }
17354
+ const my_token = tokens[my_i];
17355
+ if (my_token.range.start.line < min_line) {
17356
+ break;
17357
+ }
17358
+ if (is_swallowed_continuation_terminator(my_token, my_i > 0 && tokens[my_i - 1].type === "CONTINUATION")) {
17359
+ continue;
17360
+ }
17361
+ if (LOGICAL_BOUNDARY_TYPES.has(my_token.type)) {
17362
+ boundary_index = my_i;
17363
+ break;
17364
+ }
17365
+ }
17366
+ let boundary_opens_embedded_body = false;
17367
+ if (boundary_index >= 0) {
17368
+ const my_boundary_type = tokens[boundary_index].type;
17369
+ if (my_boundary_type === "MATA_START" || my_boundary_type === "PYTHON_START") {
17370
+ boundary_opens_embedded_body = true;
17371
+ } else if (my_boundary_type === "LBRACE") {
17372
+ for (let my_j = boundary_index - 1;my_j >= 0; my_j--) {
17373
+ const my_prev_type = tokens[my_j].type;
17374
+ if (CONTINUATION_NEUTRAL_TYPES.has(my_prev_type) || my_prev_type === "WHITESPACE") {
17375
+ continue;
17376
+ }
17377
+ boundary_opens_embedded_body = my_prev_type === "MATA_START" || my_prev_type === "PYTHON_START";
17378
+ break;
17379
+ }
17380
+ }
17381
+ }
17382
+ let first_index = -1;
17383
+ for (let my_i = boundary_index + 1;my_i < tokens.length; my_i++) {
17384
+ const my_token = tokens[my_i];
17385
+ if (my_token.type === "EOF") {
17386
+ break;
17387
+ }
17388
+ if (my_token.range.start.line < min_line) {
17389
+ continue;
17390
+ }
17391
+ if (!STATEMENT_LEADING_TRIVIA_TYPES.has(my_token.type)) {
17392
+ first_index = my_i;
17393
+ break;
17394
+ }
17395
+ }
17396
+ if (first_index !== -1 && !boundary_opens_embedded_body) {
17397
+ const my_start = tokens[first_index].range.start;
17398
+ const at_or_before_cursor = my_start.line < position.line || my_start.line === position.line && my_start.character <= position.character;
17399
+ if (at_or_before_cursor) {
17400
+ return {
17401
+ index: first_index,
17402
+ line: my_start.line,
17403
+ character: my_start.character
17404
+ };
17405
+ }
17406
+ }
17407
+ const resume_character = boundary_index >= 0 && tokens[boundary_index].range.end.line === position.line ? Math.min(tokens[boundary_index].range.end.character, position.character) : 0;
17408
+ return {
17409
+ index: first_index >= 0 ? first_index : boundary_index + 1,
17410
+ line: position.line,
17411
+ character: resume_character
17412
+ };
17413
+ }
17414
+ var LEADING_TRIVIA_TYPES, BODY_OPENER_TYPES, CONTINUATION_NEUTRAL_TYPES, LOGICAL_BOUNDARY_TYPES, STATEMENT_LEADING_TRIVIA_TYPES, MAX_STATEMENT_TOKENS = 4096;
17090
17415
  var init_statement_span = __esm(() => {
17091
17416
  LEADING_TRIVIA_TYPES = new Set([
17092
- "WHITESPACE",
17093
17417
  "COMMENT_LINE",
17094
17418
  "COMMENT_BLOCK",
17095
17419
  "CONTINUATION",
17420
+ "WHITESPACE",
17096
17421
  "STATEMENT_TERMINATOR",
17097
17422
  "DELIMIT_DIRECTIVE"
17098
17423
  ]);
@@ -17102,10 +17427,26 @@ var init_statement_span = __esm(() => {
17102
17427
  "PYTHON_START"
17103
17428
  ]);
17104
17429
  CONTINUATION_NEUTRAL_TYPES = new Set([
17105
- "WHITESPACE",
17106
17430
  "COMMENT_LINE",
17107
17431
  "COMMENT_BLOCK"
17108
17432
  ]);
17433
+ LOGICAL_BOUNDARY_TYPES = new Set([
17434
+ "STATEMENT_TERMINATOR",
17435
+ "LBRACE",
17436
+ "RBRACE",
17437
+ "DELIMIT_DIRECTIVE",
17438
+ "MATA_START",
17439
+ "PYTHON_START",
17440
+ "END_MATA",
17441
+ "END_PYTHON"
17442
+ ]);
17443
+ STATEMENT_LEADING_TRIVIA_TYPES = new Set([
17444
+ "COMMENT_LINE",
17445
+ "COMMENT_BLOCK",
17446
+ "CONTINUATION",
17447
+ "WHITESPACE",
17448
+ "STATEMENT_TERMINATOR"
17449
+ ]);
17109
17450
  });
17110
17451
 
17111
17452
  // src/analyzer/index.ts
@@ -17212,9 +17553,7 @@ class SemanticAnalyzer {
17212
17553
  };
17213
17554
  }
17214
17555
  extract_comment_directives(ast) {
17215
- for (const node of ast.nodes) {
17216
- this.extract_directives_from_node(node);
17217
- }
17556
+ this.extract_directives_from_nodes(ast.nodes);
17218
17557
  }
17219
17558
  extract_comment_directives_from_tokens(tokens, symbols, dofile_scope) {
17220
17559
  this.parse_declaration_directives_from_tokens(tokens, symbols, dofile_scope);
@@ -17348,25 +17687,45 @@ class SemanticAnalyzer {
17348
17687
  break;
17349
17688
  }
17350
17689
  }
17351
- extract_directives_from_node(node) {
17352
- if (this.has_trivia(node) && node.leadingTrivia) {
17353
- for (const trivia of node.leadingTrivia) {
17354
- this.parse_directive(trivia, node);
17690
+ extract_directives_from_nodes(nodes, outer_successor) {
17691
+ for (let my_index = 0;my_index < nodes.length; my_index++) {
17692
+ const node = nodes[my_index];
17693
+ if (this.has_trivia(node) && node.leadingTrivia) {
17694
+ for (const trivia of node.leadingTrivia) {
17695
+ this.parse_directive(trivia, node);
17696
+ }
17355
17697
  }
17356
- }
17357
- if (node.type === "program") {
17358
- for (const child of node.body) {
17359
- this.extract_directives_from_node(child);
17698
+ const following_node = nodes[my_index + 1] ?? outer_successor;
17699
+ const node_with_block_ending_trivia = node;
17700
+ if (node_with_block_ending_trivia.blockEndingTrivia) {
17701
+ for (const trivia of node_with_block_ending_trivia.blockEndingTrivia) {
17702
+ this.parse_directive(trivia, following_node);
17703
+ }
17360
17704
  }
17361
- } else if (this.is_control_flow(node)) {
17362
- for (const child of node.body) {
17363
- this.extract_directives_from_node(child);
17705
+ const the_child_body = this.directive_recursion_body(node);
17706
+ if (the_child_body) {
17707
+ this.extract_directives_from_nodes(the_child_body, following_node);
17364
17708
  }
17365
17709
  }
17366
17710
  }
17711
+ directive_recursion_body(node) {
17712
+ if (node.type === "program") {
17713
+ return node.body;
17714
+ }
17715
+ if (node.type === "frame") {
17716
+ return node.body;
17717
+ }
17718
+ if (this.is_control_flow(node)) {
17719
+ return node.body;
17720
+ }
17721
+ if (node.type === "command" && node.body) {
17722
+ return node.body;
17723
+ }
17724
+ return;
17725
+ }
17367
17726
  parse_directive(trivia, following_node) {
17368
17727
  const content = trivia.content.trim();
17369
- if (has_ignore_directive(content) || has_ignore_next_directive(content)) {
17728
+ if (following_node && (has_ignore_directive(content) || has_ignore_next_directive(content))) {
17370
17729
  const line_to_ignore = following_node.range.start.line;
17371
17730
  this.config.ignored_lines.add(line_to_ignore);
17372
17731
  }
@@ -17386,9 +17745,10 @@ class SemanticAnalyzer {
17386
17745
  if (token.range.start.line !== line) {
17387
17746
  break;
17388
17747
  }
17389
- if (token.type !== "WHITESPACE") {
17390
- return false;
17748
+ if (token.type === "WHITESPACE") {
17749
+ continue;
17391
17750
  }
17751
+ return false;
17392
17752
  }
17393
17753
  return true;
17394
17754
  }
@@ -17508,12 +17868,16 @@ class SemanticAnalyzer {
17508
17868
  }
17509
17869
  }
17510
17870
  }
17871
+ semantic_varlist(node) {
17872
+ return (node.varlist ?? []).filter((item) => item.recovery_only !== true);
17873
+ }
17511
17874
  extract_c_locals(nodes) {
17512
17875
  const c_locals = [];
17513
17876
  for (const node of nodes) {
17514
17877
  if (node.type === "command") {
17515
- if (node.fullName === "c_local" && node.varlist && node.varlist.length > 0) {
17516
- const name = node.varlist[0].name;
17878
+ const varlist = this.semantic_varlist(node);
17879
+ if (node.fullName === "c_local" && varlist.length > 0) {
17880
+ const name = varlist[0].name;
17517
17881
  if (is_valid_identifier(name)) {
17518
17882
  c_locals.push(name);
17519
17883
  }
@@ -17544,8 +17908,9 @@ class SemanticAnalyzer {
17544
17908
  const global_set = new Set;
17545
17909
  for (const node of nodes) {
17546
17910
  if (node.type === "command") {
17547
- if (node.fullName === "c_local" && node.varlist && node.varlist.length > 0) {
17548
- const macro_name = node.varlist[0].name;
17911
+ const varlist = this.semantic_varlist(node);
17912
+ if (node.fullName === "c_local" && varlist.length > 0) {
17913
+ const macro_name = varlist[0].name;
17549
17914
  if (macro_name.startsWith("`") && macro_name.endsWith("'")) {
17550
17915
  const inner_name = macro_name.slice(1, -1);
17551
17916
  const local_name = inner_name.toLowerCase();
@@ -17553,8 +17918,8 @@ class SemanticAnalyzer {
17553
17918
  local_set.add(local_name);
17554
17919
  }
17555
17920
  }
17556
- } else if (node.fullName === "global" && node.varlist && node.varlist.length > 0) {
17557
- const macro_name = node.varlist[0].name;
17921
+ } else if (node.fullName === "global" && varlist.length > 0) {
17922
+ const macro_name = varlist[0].name;
17558
17923
  if (macro_name.startsWith("`") && macro_name.endsWith("'")) {
17559
17924
  const inner_name = macro_name.slice(1, -1);
17560
17925
  const global_name = inner_name.toLowerCase();
@@ -17763,12 +18128,13 @@ class SemanticAnalyzer {
17763
18128
  };
17764
18129
  }
17765
18130
  extract_command_path(node) {
17766
- if (!node.varlist || node.varlist.length === 0) {
18131
+ const varlist = this.semantic_varlist(node);
18132
+ if (varlist.length === 0) {
17767
18133
  return null;
17768
18134
  }
17769
18135
  let raw_path = "";
17770
18136
  let has_macro = false;
17771
- const first_item = node.varlist[0];
18137
+ const first_item = varlist[0];
17772
18138
  const first_name = first_item.name;
17773
18139
  const is_complete_double_quoted = first_name.startsWith('"') && first_name.endsWith('"') && first_name.length > 1;
17774
18140
  const is_complete_single_quoted = first_name.startsWith("'") && first_name.endsWith("'") && first_name.length > 1;
@@ -17779,8 +18145,8 @@ class SemanticAnalyzer {
17779
18145
  const quote_char = first_name[0];
17780
18146
  raw_path = first_name;
17781
18147
  has_macro = raw_path.includes("`") || raw_path.includes("$");
17782
- for (let i = 1;i < node.varlist.length; i++) {
17783
- const item_name = node.varlist[i].name;
18148
+ for (let i = 1;i < varlist.length; i++) {
18149
+ const item_name = varlist[i].name;
17784
18150
  raw_path += item_name;
17785
18151
  if (item_name.includes("`") || item_name.includes("$")) {
17786
18152
  has_macro = true;
@@ -17900,28 +18266,30 @@ class SemanticAnalyzer {
17900
18266
  }
17901
18267
  }
17902
18268
  extract_scalar_symbol(node, symbols, node_index) {
17903
- if (!node.varlist || node.varlist.length === 0) {
18269
+ const varlist = this.semantic_varlist(node);
18270
+ if (varlist.length === 0) {
17904
18271
  return;
17905
18272
  }
17906
- const scalar_name = node.varlist[0].name;
17907
- this.add_or_append_definition(symbols.scalars, scalar_name, node_index, node.varlist[0].range, () => ({
18273
+ const scalar_name = varlist[0].name;
18274
+ this.add_or_append_definition(symbols.scalars, scalar_name, node_index, varlist[0].range, () => ({
17908
18275
  name: scalar_name,
17909
- location: { uri: this.uri, range: node.varlist[0].range },
18276
+ location: { uri: this.uri, range: varlist[0].range },
17910
18277
  sourceUri: this.uri,
17911
- definition_line: node.varlist[0].range.start.line
18278
+ definition_line: varlist[0].range.start.line
17912
18279
  }));
17913
18280
  }
17914
18281
  extract_matrix_symbol(node, symbols, node_index) {
17915
- if (!node.varlist || node.varlist.length === 0) {
18282
+ const varlist = this.semantic_varlist(node);
18283
+ if (varlist.length === 0) {
17916
18284
  return;
17917
18285
  }
17918
- const first = node.varlist[0]?.name;
17919
- const second = node.varlist[1]?.name;
18286
+ const first = varlist[0]?.name;
18287
+ const second = varlist[1]?.name;
17920
18288
  const matrix_name = first && first === "define" ? second : first;
17921
18289
  if (!matrix_name) {
17922
18290
  return;
17923
18291
  }
17924
- const name_range = first && first === "define" && node.varlist[1] ? node.varlist[1].range : node.varlist[0].range;
18292
+ const name_range = first && first === "define" && varlist[1] ? varlist[1].range : varlist[0].range;
17925
18293
  this.add_or_append_definition(symbols.matrices, matrix_name, node_index, name_range, () => ({
17926
18294
  name: matrix_name,
17927
18295
  location: { uri: this.uri, range: name_range },
@@ -17930,22 +18298,23 @@ class SemanticAnalyzer {
17930
18298
  }));
17931
18299
  }
17932
18300
  pick_new_variable(node) {
17933
- if (!node.varlist || node.varlist.length === 0) {
18301
+ const varlist = this.semantic_varlist(node);
18302
+ if (varlist.length === 0) {
17934
18303
  return;
17935
18304
  }
17936
- if (is_stata_storage_type(node.varlist[0].name)) {
17937
- if (node.varlist.length > 1) {
17938
- return node.varlist[1];
18305
+ if (is_stata_storage_type(varlist[0].name)) {
18306
+ if (varlist.length > 1) {
18307
+ return varlist[1];
17939
18308
  }
17940
18309
  this.current_diagnostics.push({
17941
- message: `Missing variable name after storage type \`${node.varlist[0].name}'`,
17942
- range: node.varlist[0].range,
18310
+ message: `Missing variable name after storage type \`${varlist[0].name}'`,
18311
+ range: varlist[0].range,
17943
18312
  code: "MISSING_VARIABLE_NAME" /* MISSING_VARIABLE_NAME */,
17944
18313
  severity: "error"
17945
18314
  });
17946
18315
  return;
17947
18316
  }
17948
- return node.varlist[0];
18317
+ return varlist[0];
17949
18318
  }
17950
18319
  extract_gen_variable(node, symbols) {
17951
18320
  const new_var = this.pick_new_variable(node);
@@ -17978,10 +18347,11 @@ class SemanticAnalyzer {
17978
18347
  symbols.variables.set(new_var.name, var_symbol);
17979
18348
  }
17980
18349
  extract_input_variables(node, symbols) {
17981
- if (!node.varlist) {
18350
+ const varlist = this.semantic_varlist(node);
18351
+ if (varlist.length === 0) {
17982
18352
  return;
17983
18353
  }
17984
- for (const var_node of node.varlist) {
18354
+ for (const var_node of varlist) {
17985
18355
  if (this.is_macro_reference(var_node.name)) {
17986
18356
  continue;
17987
18357
  }
@@ -18018,28 +18388,29 @@ class SemanticAnalyzer {
18018
18388
  return false;
18019
18389
  }
18020
18390
  extract_rename_variables(node, symbols) {
18021
- if (!node.varlist || node.varlist.length < 2) {
18391
+ const varlist = this.semantic_varlist(node);
18392
+ if (varlist.length < 2) {
18022
18393
  return;
18023
18394
  }
18024
- if (node.varlist.length !== 2 && !(node.varlist[0].name.startsWith("(") && node.varlist[1].name.startsWith("("))) {
18395
+ if (varlist.length !== 2 && !(varlist[0].name.startsWith("(") && varlist[1].name.startsWith("("))) {
18025
18396
  return;
18026
18397
  }
18027
- const first_item = node.varlist[0].name;
18028
- const second_item = node.varlist[1].name;
18398
+ const first_item = varlist[0].name;
18399
+ const second_item = varlist[1].name;
18029
18400
  if (first_item.startsWith("(") && second_item.startsWith("(")) {
18030
18401
  if (this.contains_wildcard(first_item) || this.contains_wildcard(second_item)) {
18031
18402
  return;
18032
18403
  }
18033
- this.extract_grouped_rename_variables(second_item, node.varlist[1].range, symbols);
18404
+ this.extract_grouped_rename_variables(second_item, varlist[1].range, symbols);
18034
18405
  return;
18035
18406
  }
18036
18407
  if (this.contains_wildcard(first_item) || this.contains_wildcard(second_item)) {
18037
18408
  return;
18038
18409
  }
18039
- if (this.has_adjacent_wildcard_token(node.varlist[0].range) || this.has_adjacent_wildcard_token(node.varlist[1].range)) {
18410
+ if (this.has_adjacent_wildcard_token(varlist[0].range) || this.has_adjacent_wildcard_token(varlist[1].range)) {
18040
18411
  return;
18041
18412
  }
18042
- const new_var = node.varlist[1];
18413
+ const new_var = varlist[1];
18043
18414
  if (this.is_macro_reference(new_var.name)) {
18044
18415
  return;
18045
18416
  }
@@ -18052,7 +18423,7 @@ class SemanticAnalyzer {
18052
18423
  symbols.variables.set(new_var.name, var_symbol);
18053
18424
  }
18054
18425
  extract_grouped_rename_variables(group_content, group_range, symbols) {
18055
- const inner = group_content.slice(1, -1).trim();
18426
+ const inner = (group_content.endsWith(")") ? group_content.slice(1, -1) : group_content.slice(1)).trim();
18056
18427
  const the_names = inner.split(/\s+/).filter((n) => n.length > 0);
18057
18428
  for (const my_name of the_names) {
18058
18429
  if (this.contains_wildcard(my_name)) {
@@ -18071,14 +18442,15 @@ class SemanticAnalyzer {
18071
18442
  }
18072
18443
  }
18073
18444
  extract_confirm_variable(node, symbols) {
18074
- if (!node.varlist || node.varlist.length < 2) {
18445
+ const varlist = this.semantic_varlist(node);
18446
+ if (varlist.length < 2) {
18075
18447
  return;
18076
18448
  }
18077
- const first_item = node.varlist[0].name.toLowerCase();
18449
+ const first_item = varlist[0].name.toLowerCase();
18078
18450
  if (first_item !== "variable" && first_item !== "var") {
18079
18451
  return;
18080
18452
  }
18081
- const var_node = node.varlist[1];
18453
+ const var_node = varlist[1];
18082
18454
  if (this.is_macro_reference(var_node.name)) {
18083
18455
  return;
18084
18456
  }
@@ -18091,10 +18463,11 @@ class SemanticAnalyzer {
18091
18463
  symbols.variables.set(var_node.name, var_symbol);
18092
18464
  }
18093
18465
  extract_tempvar_macro(node, symbols, current_scope, node_index) {
18094
- if (!node.varlist) {
18466
+ const varlist = this.semantic_varlist(node);
18467
+ if (varlist.length === 0) {
18095
18468
  return;
18096
18469
  }
18097
- for (const var_node of node.varlist) {
18470
+ for (const var_node of varlist) {
18098
18471
  this.register_local_macro(current_scope, symbols, var_node.name, node_index, var_node.range, () => ({
18099
18472
  name: var_node.name,
18100
18473
  scope: "local",
@@ -18107,8 +18480,9 @@ class SemanticAnalyzer {
18107
18480
  }
18108
18481
  }
18109
18482
  extract_unab_macro(node, symbols, current_scope, node_index) {
18110
- if (node.varlist && node.varlist.length > 0) {
18111
- const macro_node = node.varlist[0];
18483
+ const varlist = this.semantic_varlist(node);
18484
+ if (varlist.length > 0) {
18485
+ const macro_node = varlist[0];
18112
18486
  const macro_name = macro_node.name;
18113
18487
  this.register_local_macro(current_scope, symbols, macro_name, node_index, macro_node.range, () => ({
18114
18488
  name: macro_name,
@@ -18122,8 +18496,9 @@ class SemanticAnalyzer {
18122
18496
  }
18123
18497
  }
18124
18498
  extract_args_macros(node, symbols, current_scope, node_index) {
18125
- if (node.varlist && node.varlist.length > 0) {
18126
- for (const my_var_node of node.varlist) {
18499
+ const varlist = this.semantic_varlist(node);
18500
+ if (varlist.length > 0) {
18501
+ for (const my_var_node of varlist) {
18127
18502
  const macro_name = my_var_node.name;
18128
18503
  this.register_local_macro(current_scope, symbols, macro_name, node_index, my_var_node.range, () => ({
18129
18504
  name: macro_name,
@@ -18138,12 +18513,13 @@ class SemanticAnalyzer {
18138
18513
  }
18139
18514
  }
18140
18515
  extract_gettoken_macros(node, symbols, current_scope, node_index) {
18141
- if (!node.varlist || node.varlist.length === 0) {
18516
+ const varlist = this.semantic_varlist(node);
18517
+ if (varlist.length === 0) {
18142
18518
  return;
18143
18519
  }
18144
- const max_macros = Math.min(node.varlist.length, 2);
18520
+ const max_macros = Math.min(varlist.length, 2);
18145
18521
  for (let i = 0;i < max_macros; i++) {
18146
- const my_var_node = node.varlist[i];
18522
+ const my_var_node = varlist[i];
18147
18523
  const macro_name = my_var_node.name;
18148
18524
  if (!is_valid_identifier(macro_name)) {
18149
18525
  continue;
@@ -18160,13 +18536,14 @@ class SemanticAnalyzer {
18160
18536
  }
18161
18537
  }
18162
18538
  extract_file_read_macro(node, symbols, current_scope, node_index) {
18163
- if (!node.varlist || node.varlist.length < 3) {
18539
+ const varlist = this.semantic_varlist(node);
18540
+ if (varlist.length < 3) {
18164
18541
  return;
18165
18542
  }
18166
- if (node.varlist[0].name !== "read") {
18543
+ if (varlist[0].name !== "read") {
18167
18544
  return;
18168
18545
  }
18169
- const macro_node = node.varlist[2];
18546
+ const macro_node = varlist[2];
18170
18547
  const macro_name = macro_node.name;
18171
18548
  if (!is_valid_identifier(macro_name)) {
18172
18549
  return;
@@ -18240,6 +18617,9 @@ class SemanticAnalyzer {
18240
18617
  if (!builtin_cmd && !program_options)
18241
18618
  return false;
18242
18619
  for (const my_option of node.options) {
18620
+ if (my_option.argument_unclosed) {
18621
+ continue;
18622
+ }
18243
18623
  let matches = false;
18244
18624
  if (builtin_cmd) {
18245
18625
  for (const opt of builtin_cmd.local_options) {
@@ -18266,7 +18646,7 @@ class SemanticAnalyzer {
18266
18646
  if (node.fullName !== "macro" && node.fullName !== "mac") {
18267
18647
  return { names: [], unknown: false };
18268
18648
  }
18269
- const the_args = node.varlist ?? [];
18649
+ const the_args = this.semantic_varlist(node);
18270
18650
  if (the_args.length < 2 || !/^dr(o(p)?)?$/.test(the_args[0].name)) {
18271
18651
  return { names: [], unknown: false };
18272
18652
  }
@@ -18297,18 +18677,20 @@ class SemanticAnalyzer {
18297
18677
  names.push(name);
18298
18678
  };
18299
18679
  if (cmd_name === "tempvar" || cmd_name === "tempfile" || cmd_name === "tempname" || cmd_name === "args") {
18300
- for (const my_var of node.varlist ?? [])
18680
+ for (const my_var of this.semantic_varlist(node))
18301
18681
  push_literal(my_var.name);
18302
18682
  } else if (cmd_name === "unab") {
18303
- push_literal(node.varlist?.[0]?.name);
18683
+ push_literal(this.semantic_varlist(node)[0]?.name);
18304
18684
  } else if (cmd_name === "gettoken" || cmd_name === "gettok") {
18305
- const max_macros = Math.min(node.varlist?.length ?? 0, 2);
18685
+ const varlist = this.semantic_varlist(node);
18686
+ const max_macros = Math.min(varlist.length, 2);
18306
18687
  for (let i = 0;i < max_macros; i++) {
18307
- push_literal(node.varlist[i].name);
18688
+ push_literal(varlist[i].name);
18308
18689
  }
18309
18690
  } else if (cmd_name === "file" || cmd_name === "fil" || cmd_name === "fi") {
18310
- if ((node.varlist?.length ?? 0) >= 3 && node.varlist[0].name === "read") {
18311
- push_literal(node.varlist[2].name);
18691
+ const varlist = this.semantic_varlist(node);
18692
+ if (varlist.length >= 3 && varlist[0].name === "read") {
18693
+ push_literal(varlist[2].name);
18312
18694
  }
18313
18695
  }
18314
18696
  return names;
@@ -18347,6 +18729,9 @@ class SemanticAnalyzer {
18347
18729
  }
18348
18730
  const the_created = [];
18349
18731
  for (const option of node.options) {
18732
+ if (option.argument_unclosed) {
18733
+ continue;
18734
+ }
18350
18735
  const parse_result = parse_option_argument(option.argument);
18351
18736
  if (!parse_result.is_literal || !parse_result.identifier) {
18352
18737
  continue;
@@ -18696,19 +19081,17 @@ class SemanticAnalyzer {
18696
19081
  if (cmd_name === "generate" || cmd_name === "gen" || cmd_name === "egen" || cmd_name === "input" || cmd_name === "tempvar" || cmd_name === "unab") {
18697
19082
  return;
18698
19083
  }
18699
- if (node.varlist) {
18700
- for (const var_node of node.varlist) {
18701
- const is_defined = this.is_variable_defined(var_node.name, symbols, var_node.range.start.line);
18702
- if (!is_defined) {
18703
- diagnostics.push({
18704
- message: format_undefined_variable_message(var_node.name),
18705
- range: var_node.range,
18706
- code: "UNDEFINED_VARIABLE" /* UNDEFINED_VARIABLE */,
18707
- severity: "information",
18708
- symbol_name: var_node.name,
18709
- reference_kind: "variable"
18710
- });
18711
- }
19084
+ for (const var_node of this.semantic_varlist(node)) {
19085
+ const is_defined = this.is_variable_defined(var_node.name, symbols, var_node.range.start.line);
19086
+ if (!is_defined) {
19087
+ diagnostics.push({
19088
+ message: format_undefined_variable_message(var_node.name),
19089
+ range: var_node.range,
19090
+ code: "UNDEFINED_VARIABLE" /* UNDEFINED_VARIABLE */,
19091
+ severity: "information",
19092
+ symbol_name: var_node.name,
19093
+ reference_kind: "variable"
19094
+ });
18712
19095
  }
18713
19096
  }
18714
19097
  }
@@ -19750,12 +20133,10 @@ class ContextTracker {
19750
20133
  is_single_line: my_token.type === "MATA_INLINE"
19751
20134
  };
19752
20135
  if (my_token.type === "MATA_INLINE") {
19753
- my_current_range.end_delimiter = {
19754
- command: my_token.value,
19755
- range: my_token.range
19756
- };
19757
- this.context_ranges.push(this.complete_context_range_from_token(my_current_range));
20136
+ const my_inline_end = inline_embedded_statement_end(tokens, my_i);
20137
+ this.context_ranges.push(this.complete_context_range_from_token(my_current_range, my_inline_end.end_line));
19758
20138
  my_current_range = null;
20139
+ my_i = my_inline_end.end_index;
19759
20140
  } else {
19760
20141
  this.context_stack.push("mata" /* MATA */);
19761
20142
  my_current_context = "mata" /* MATA */;
@@ -19777,12 +20158,10 @@ class ContextTracker {
19777
20158
  is_single_line: my_token.type === "PYTHON_INLINE"
19778
20159
  };
19779
20160
  if (my_token.type === "PYTHON_INLINE") {
19780
- my_current_range.end_delimiter = {
19781
- command: my_token.value,
19782
- range: my_token.range
19783
- };
19784
- this.context_ranges.push(this.complete_context_range_from_token(my_current_range));
20161
+ const my_inline_end = inline_embedded_statement_end(tokens, my_i);
20162
+ this.context_ranges.push(this.complete_context_range_from_token(my_current_range, my_inline_end.end_line));
19785
20163
  my_current_range = null;
20164
+ my_i = my_inline_end.end_index;
19786
20165
  } else {
19787
20166
  this.context_stack.push("python" /* PYTHON */);
19788
20167
  my_current_context = "python" /* PYTHON */;
@@ -20086,11 +20465,11 @@ class ContextTracker {
20086
20465
  }
20087
20466
  return false;
20088
20467
  }
20089
- complete_context_range_from_token(partial_range) {
20468
+ complete_context_range_from_token(partial_range, single_line_end_line) {
20090
20469
  const my_start_line = partial_range.start_delimiter.range.start.line;
20091
20470
  let my_end_line;
20092
20471
  if (partial_range.is_single_line) {
20093
- my_end_line = my_start_line;
20472
+ my_end_line = single_line_end_line ?? my_start_line;
20094
20473
  } else {
20095
20474
  if (partial_range.end_delimiter) {
20096
20475
  my_end_line = partial_range.end_delimiter.range.start.line - 1;
@@ -20278,6 +20657,7 @@ var init_context_tracker = __esm(() => {
20278
20657
  init_types2();
20279
20658
  init_types();
20280
20659
  init_block_comment_utils();
20660
+ init_statement_span();
20281
20661
  CONTEXT_TRIVIA_TOKEN_TYPES = new Set([
20282
20662
  "COMMENT_LINE",
20283
20663
  "COMMENT_BLOCK",
@@ -20961,6 +21341,221 @@ var init_document_store = __esm(() => {
20961
21341
  import_vscode_languageserver = __toESM(require_main4(), 1);
20962
21342
  });
20963
21343
 
21344
+ // src/utils/lru-cache.ts
21345
+ class LRUCache {
21346
+ cache = new Map;
21347
+ max_size;
21348
+ hits = 0;
21349
+ misses = 0;
21350
+ evictions = 0;
21351
+ current_generation = 0;
21352
+ constructor(max_size = 100) {
21353
+ this.max_size = max_size;
21354
+ }
21355
+ get(key) {
21356
+ const entry = this.cache.get(key);
21357
+ if (entry !== undefined) {
21358
+ this.hits++;
21359
+ entry.access_count++;
21360
+ this.cache.delete(key);
21361
+ this.cache.set(key, entry);
21362
+ return entry.value;
21363
+ } else {
21364
+ this.misses++;
21365
+ }
21366
+ return;
21367
+ }
21368
+ set(key, value) {
21369
+ this.cache.delete(key);
21370
+ if (this.cache.size >= this.max_size) {
21371
+ this.evict_one();
21372
+ }
21373
+ this.cache.set(key, {
21374
+ value,
21375
+ generation: this.current_generation,
21376
+ access_count: 1
21377
+ });
21378
+ }
21379
+ evict_one() {
21380
+ let victim_key;
21381
+ let victim_score = Infinity;
21382
+ for (const [key, entry] of this.cache) {
21383
+ const score = entry.generation * 1000 + entry.access_count;
21384
+ if (score < victim_score) {
21385
+ victim_score = score;
21386
+ victim_key = key;
21387
+ }
21388
+ }
21389
+ if (victim_key !== undefined) {
21390
+ this.cache.delete(victim_key);
21391
+ this.evictions++;
21392
+ }
21393
+ }
21394
+ advance_generation() {
21395
+ this.current_generation++;
21396
+ }
21397
+ trim_to_top_n(n) {
21398
+ if (this.cache.size <= n)
21399
+ return;
21400
+ const entries = Array.from(this.cache.entries()).sort((a, b) => b[1].access_count - a[1].access_count);
21401
+ this.cache.clear();
21402
+ for (let i = 0;i < Math.min(n, entries.length); i++) {
21403
+ this.cache.set(entries[i][0], entries[i][1]);
21404
+ }
21405
+ this.evictions += entries.length - n;
21406
+ }
21407
+ has(key) {
21408
+ return this.cache.has(key);
21409
+ }
21410
+ clear() {
21411
+ this.cache.clear();
21412
+ }
21413
+ size() {
21414
+ return this.cache.size;
21415
+ }
21416
+ get_stats() {
21417
+ return { hits: this.hits, misses: this.misses, evictions: this.evictions };
21418
+ }
21419
+ }
21420
+
21421
+ class CompletionPrefixCache {
21422
+ cache;
21423
+ command_db_version = 0;
21424
+ workspace_version = 0;
21425
+ top_n_threshold;
21426
+ constructor(max_size = 100, top_n_threshold = 200) {
21427
+ this.cache = new LRUCache(max_size);
21428
+ this.top_n_threshold = top_n_threshold;
21429
+ }
21430
+ make_key(prefix, context, document_version = 0) {
21431
+ return `${context}:${prefix}:${this.command_db_version}:${this.workspace_version}:${document_version}`;
21432
+ }
21433
+ get_with_context(prefix, context, document_version = 0) {
21434
+ return this.cache.get(this.make_key(prefix, context, document_version));
21435
+ }
21436
+ set_with_context(prefix, context, value, document_version = 0) {
21437
+ const trimmed_value = this.apply_limit(value);
21438
+ this.cache.set(this.make_key(prefix, context, document_version), trimmed_value);
21439
+ return trimmed_value;
21440
+ }
21441
+ apply_limit(value) {
21442
+ if (this.top_n_threshold > 0 && value.length > this.top_n_threshold) {
21443
+ return value.slice(0, this.top_n_threshold);
21444
+ }
21445
+ return value;
21446
+ }
21447
+ has_with_context(prefix, context, document_version = 0) {
21448
+ return this.cache.has(this.make_key(prefix, context, document_version));
21449
+ }
21450
+ invalidate_on_db_change(new_version) {
21451
+ if (new_version !== this.command_db_version) {
21452
+ this.command_db_version = new_version;
21453
+ this.cache.clear();
21454
+ this.cache.advance_generation();
21455
+ }
21456
+ }
21457
+ invalidate_on_workspace_change(new_version) {
21458
+ if (new_version !== this.workspace_version) {
21459
+ this.workspace_version = new_version;
21460
+ this.cache.clear();
21461
+ this.cache.advance_generation();
21462
+ }
21463
+ }
21464
+ clear() {
21465
+ this.cache.clear();
21466
+ }
21467
+ size() {
21468
+ return this.cache.size();
21469
+ }
21470
+ get_stats() {
21471
+ return this.cache.get_stats();
21472
+ }
21473
+ }
21474
+ var BoundedLruMap;
21475
+ var init_lru_cache = __esm(() => {
21476
+ BoundedLruMap = class BoundedLruMap {
21477
+ map = new Map;
21478
+ max_size;
21479
+ on_evict;
21480
+ constructor(max_size, options) {
21481
+ this.max_size = Math.max(1, Math.floor(max_size));
21482
+ this.on_evict = options?.on_evict;
21483
+ }
21484
+ get(key) {
21485
+ if (!this.map.has(key)) {
21486
+ return;
21487
+ }
21488
+ const value = this.map.get(key);
21489
+ this.map.delete(key);
21490
+ this.map.set(key, value);
21491
+ return value;
21492
+ }
21493
+ peek(key) {
21494
+ return this.map.get(key);
21495
+ }
21496
+ has(key) {
21497
+ return this.map.has(key);
21498
+ }
21499
+ touch(key) {
21500
+ if (!this.map.has(key)) {
21501
+ return;
21502
+ }
21503
+ const value = this.map.get(key);
21504
+ this.map.delete(key);
21505
+ this.map.set(key, value);
21506
+ }
21507
+ set(key, value) {
21508
+ if (this.map.has(key)) {
21509
+ this.map.delete(key);
21510
+ } else if (this.map.size >= this.max_size) {
21511
+ this.evict_oldest();
21512
+ }
21513
+ this.map.set(key, value);
21514
+ return this;
21515
+ }
21516
+ delete(key) {
21517
+ return this.map.delete(key);
21518
+ }
21519
+ clear() {
21520
+ this.map.clear();
21521
+ }
21522
+ get size() {
21523
+ return this.map.size;
21524
+ }
21525
+ get capacity() {
21526
+ return this.max_size;
21527
+ }
21528
+ set_max_size(new_max_size) {
21529
+ this.max_size = Math.max(1, Math.floor(new_max_size));
21530
+ while (this.map.size > this.max_size) {
21531
+ this.evict_oldest();
21532
+ }
21533
+ }
21534
+ keys() {
21535
+ return this.map.keys();
21536
+ }
21537
+ values() {
21538
+ return this.map.values();
21539
+ }
21540
+ entries() {
21541
+ return this.map.entries();
21542
+ }
21543
+ [Symbol.iterator]() {
21544
+ return this.map[Symbol.iterator]();
21545
+ }
21546
+ evict_oldest() {
21547
+ const oldest = this.map.keys().next();
21548
+ if (oldest.done) {
21549
+ return;
21550
+ }
21551
+ const oldest_key = oldest.value;
21552
+ const oldest_value = this.map.get(oldest_key);
21553
+ this.map.delete(oldest_key);
21554
+ this.on_evict?.(oldest_key, oldest_value);
21555
+ }
21556
+ };
21557
+ });
21558
+
20964
21559
  // src/utils/dofile-locals.ts
20965
21560
  function is_dofile_local(symbol) {
20966
21561
  return symbol.containingScope === "dofile";
@@ -21261,14 +21856,20 @@ class ForwardScopeResolver {
21261
21856
  workspace_roots = [];
21262
21857
  resolve_fs;
21263
21858
  forward_closure_memo_enabled = true;
21264
- forward_closure_memo = new Map;
21859
+ forward_closure_memo = new BoundedLruMap(DEFAULT_MAX_CACHED_FORWARD_CLOSURES, {
21860
+ on_evict: (key, entry) => {
21861
+ this.memo_metrics.evictions++;
21862
+ this.prune_memo_uri_to_keys_for_entry(key, entry);
21863
+ }
21864
+ });
21265
21865
  memo_uri_to_keys = new Map;
21266
21866
  last_seen_graph_version;
21267
21867
  memo_invalidation_epoch = 0;
21268
21868
  memo_metrics = {
21269
21869
  hits: 0,
21270
21870
  misses: 0,
21271
- invalidations: 0
21871
+ invalidations: 0,
21872
+ evictions: 0
21272
21873
  };
21273
21874
  standalone_in_flight = new Set;
21274
21875
  active_probe_collectors = [];
@@ -21291,8 +21892,16 @@ class ForwardScopeResolver {
21291
21892
  get_forward_closure_metrics() {
21292
21893
  return { ...this.memo_metrics };
21293
21894
  }
21895
+ get_forward_closure_memo_size() {
21896
+ return this.forward_closure_memo.size;
21897
+ }
21294
21898
  reset_forward_closure_metrics() {
21295
- this.memo_metrics = { hits: 0, misses: 0, invalidations: 0 };
21899
+ this.memo_metrics = {
21900
+ hits: 0,
21901
+ misses: 0,
21902
+ invalidations: 0,
21903
+ evictions: 0
21904
+ };
21296
21905
  }
21297
21906
  invalidate_forward_closure_for_uri(uri) {
21298
21907
  this.memo_invalidation_epoch++;
@@ -21316,12 +21925,16 @@ class ForwardScopeResolver {
21316
21925
  this.memo_uri_to_keys.clear();
21317
21926
  }
21318
21927
  delete_memo_entry(key) {
21319
- const my_entry = this.forward_closure_memo.get(key);
21928
+ const my_entry = this.forward_closure_memo.peek(key);
21320
21929
  if (!my_entry) {
21321
21930
  return false;
21322
21931
  }
21323
21932
  this.forward_closure_memo.delete(key);
21324
- for (const my_dep_uri of my_entry.dependent_uris) {
21933
+ this.prune_memo_uri_to_keys_for_entry(key, my_entry);
21934
+ return true;
21935
+ }
21936
+ prune_memo_uri_to_keys_for_entry(key, entry) {
21937
+ for (const my_dep_uri of entry.dependent_uris) {
21325
21938
  const my_key_set = this.memo_uri_to_keys.get(my_dep_uri);
21326
21939
  if (my_key_set) {
21327
21940
  my_key_set.delete(key);
@@ -21330,7 +21943,9 @@ class ForwardScopeResolver {
21330
21943
  }
21331
21944
  }
21332
21945
  }
21333
- return true;
21946
+ }
21947
+ set_forward_closure_memo_capacity(max_entries) {
21948
+ this.forward_closure_memo.set_max_size(max_entries);
21334
21949
  }
21335
21950
  is_memo_usable() {
21336
21951
  return this.dependency_graph === undefined || this.dependency_graph.is_scan_complete();
@@ -21743,6 +22358,10 @@ class ForwardScopeResolver {
21743
22358
  });
21744
22359
  }
21745
22360
  store_memo_entry(key, entry) {
22361
+ const my_previous = this.forward_closure_memo.peek(key);
22362
+ if (my_previous) {
22363
+ this.prune_memo_uri_to_keys_for_entry(key, my_previous);
22364
+ }
21746
22365
  this.forward_closure_memo.set(key, entry);
21747
22366
  for (const my_dep_uri of entry.dependent_uris) {
21748
22367
  let my_key_set = this.memo_uri_to_keys.get(my_dep_uri);
@@ -21916,13 +22535,14 @@ class ForwardScopeResolver {
21916
22535
  this.clear_forward_closure_memo();
21917
22536
  }
21918
22537
  }
21919
- var DEFAULT_CONFIG;
22538
+ var DEFAULT_CONFIG, DEFAULT_MAX_CACHED_FORWARD_CLOSURES = 2000;
21920
22539
  var init_forward_scope_resolver = __esm(() => {
21921
22540
  init_esm();
21922
22541
  init_types();
21923
22542
  init_analyzer();
21924
22543
  init_file_path_utils();
21925
22544
  init_workspace_roots();
22545
+ init_lru_cache();
21926
22546
  init_dofile_locals();
21927
22547
  init_visible_symbols();
21928
22548
  DEFAULT_CONFIG = {
@@ -21981,13 +22601,23 @@ class ScopeResolver {
21981
22601
  workspace_roots = [];
21982
22602
  dependency_graph;
21983
22603
  resolve_fs;
21984
- constructor(logger2, content_provider) {
22604
+ constructor(logger2, content_provider, cache_capacities) {
21985
22605
  this.directive_parser = new DirectiveParser;
21986
22606
  this.lexer = new StataLexer;
21987
22607
  this.parser = new StataParser;
21988
22608
  this.analyzer = new SemanticAnalyzer;
21989
- this.file_cache = new Map;
21990
- this.scope_cache = new Map;
22609
+ this.file_cache = new BoundedLruMap(cache_capacities?.max_cached_files ?? DEFAULT_MAX_CACHED_FILES, {
22610
+ on_evict: (_key, entry) => {
22611
+ this.cache_metrics.file.evictions++;
22612
+ this.forward_scope_resolver?.invalidate_forward_closure_for_uri?.(entry.uri);
22613
+ }
22614
+ });
22615
+ this.scope_cache = new BoundedLruMap(cache_capacities?.max_cached_scopes ?? DEFAULT_MAX_CACHED_SCOPES, {
22616
+ on_evict: (key) => {
22617
+ this.cache_metrics.scope.evictions++;
22618
+ this.prune_uri_to_cache_keys_for_key(key);
22619
+ }
22620
+ });
21991
22621
  this.uri_to_cache_keys = new Map;
21992
22622
  this.cache_metrics = this.create_metrics();
21993
22623
  this.logger = logger2;
@@ -22082,8 +22712,8 @@ class ScopeResolver {
22082
22712
  }
22083
22713
  create_metrics() {
22084
22714
  const metrics = {
22085
- scope: { hits: 0, misses: 0, invalidations: 0 },
22086
- file: { hits: 0, misses: 0, invalidations: 0 }
22715
+ scope: { hits: 0, misses: 0, invalidations: 0, evictions: 0 },
22716
+ file: { hits: 0, misses: 0, invalidations: 0, evictions: 0 }
22087
22717
  };
22088
22718
  return {
22089
22719
  ...metrics,
@@ -22098,6 +22728,24 @@ class ScopeResolver {
22098
22728
  }
22099
22729
  };
22100
22730
  }
22731
+ set_cache_capacities(capacities) {
22732
+ if (capacities.max_cached_files !== undefined) {
22733
+ this.file_cache.set_max_size(capacities.max_cached_files);
22734
+ }
22735
+ if (capacities.max_cached_scopes !== undefined) {
22736
+ this.scope_cache.set_max_size(capacities.max_cached_scopes);
22737
+ }
22738
+ }
22739
+ prune_uri_to_cache_keys_for_key(cache_key) {
22740
+ const key_uri = this.extract_uri_from_cache_key(cache_key);
22741
+ const key_set = this.uri_to_cache_keys.get(key_uri);
22742
+ if (key_set) {
22743
+ key_set.delete(cache_key);
22744
+ if (key_set.size === 0) {
22745
+ this.uri_to_cache_keys.delete(key_uri);
22746
+ }
22747
+ }
22748
+ }
22101
22749
  make_file_cache_key(uri, working_directory) {
22102
22750
  return working_directory ? `${uri}|${working_directory}` : uri;
22103
22751
  }
@@ -22110,14 +22758,7 @@ class ScopeResolver {
22110
22758
  }
22111
22759
  for (const my_key of keys_to_remove) {
22112
22760
  this.scope_cache.delete(my_key);
22113
- const key_uri = this.extract_uri_from_cache_key(my_key);
22114
- const key_set = this.uri_to_cache_keys.get(key_uri);
22115
- if (key_set) {
22116
- key_set.delete(my_key);
22117
- if (key_set.size === 0) {
22118
- this.uri_to_cache_keys.delete(key_uri);
22119
- }
22120
- }
22761
+ this.prune_uri_to_cache_keys_for_key(my_key);
22121
22762
  }
22122
22763
  return keys_to_remove.length;
22123
22764
  }
@@ -22638,9 +23279,9 @@ class ScopeResolver {
22638
23279
  ...config,
22639
23280
  backward_dependencies: "explicit"
22640
23281
  };
22641
- return this.discover_working_directory(the_backward_directives, new Set, 0, my_config, new Map, current_uri);
23282
+ return this.discover_working_directory(the_backward_directives, new Set, 0, my_config, new Map, current_uri, undefined, true);
22642
23283
  }
22643
- async discover_working_directory(directives, visited, depth, config, request_cache, current_uri, token) {
23284
+ async discover_working_directory(directives, visited, depth, config, request_cache, current_uri, token, skip_backward_registration) {
22644
23285
  if (depth > config.max_backward_depth) {
22645
23286
  return;
22646
23287
  }
@@ -22661,7 +23302,8 @@ class ScopeResolver {
22661
23302
  try {
22662
23303
  my_parent_result = await this.get_parsed_file(my_parent_uri, my_real_path, {
22663
23304
  request_cache,
22664
- backward_dependencies: config.backward_dependencies ?? "auto"
23305
+ backward_dependencies: config.backward_dependencies ?? "auto",
23306
+ skip_backward_registration
22665
23307
  });
22666
23308
  } catch (error) {
22667
23309
  my_parent_result = { error: String(error) };
@@ -22691,7 +23333,7 @@ class ScopeResolver {
22691
23333
  } = this.get_effective_backward_directives(my_parent_uri, my_parent_result.directives, config, my_parent_result.is_standalone);
22692
23334
  if (effective_parent_directives.length > 0) {
22693
23335
  visited.add(my_parent_uri);
22694
- const my_deeper_wd = await this.discover_working_directory(effective_parent_directives, visited, depth + 1, config, request_cache, my_parent_uri, token);
23336
+ const my_deeper_wd = await this.discover_working_directory(effective_parent_directives, visited, depth + 1, config, request_cache, my_parent_uri, token, skip_backward_registration);
22695
23337
  visited.delete(my_parent_uri);
22696
23338
  if (my_deeper_wd) {
22697
23339
  return my_deeper_wd;
@@ -22930,8 +23572,9 @@ class ScopeResolver {
22930
23572
  }
22931
23573
  parse_file(uri, content) {
22932
23574
  const content_hash = this.hash_content(content);
22933
- const cached = this.file_cache.get(uri);
23575
+ const cached = this.file_cache.peek(uri);
22934
23576
  if (cached && cached.content_hash === content_hash) {
23577
+ this.file_cache.touch(uri);
22935
23578
  return {
22936
23579
  symbols: cached.symbols,
22937
23580
  directives: cached.directives,
@@ -22948,6 +23591,7 @@ class ScopeResolver {
22948
23591
  try {
22949
23592
  const my_parse_result = this.parse_content(uri, content);
22950
23593
  this.file_cache.set(uri, {
23594
+ uri,
22951
23595
  content,
22952
23596
  content_hash,
22953
23597
  size: Buffer.byteLength(content, "utf8"),
@@ -23088,11 +23732,14 @@ class ScopeResolver {
23088
23732
  async _get_parsed_file_impl(uri, fs_path, options) {
23089
23733
  const inherited_wd = options?.working_directory;
23090
23734
  const cache_key = this.make_file_cache_key(uri, inherited_wd);
23091
- const cached = this.file_cache.get(cache_key);
23735
+ const cached = this.file_cache.peek(cache_key);
23092
23736
  if (options?.skip_disk_if_cached && cached) {
23093
23737
  this.log(`[get_parsed_file] file_cache HIT for ${cache_key} (skip_disk_if_cached)`);
23094
23738
  this.cache_metrics.file.hits++;
23095
- this.upgrade_registration_on_cache_hit(uri, cached, options?.backward_dependencies);
23739
+ this.file_cache.touch(cache_key);
23740
+ if (!options?.skip_backward_registration) {
23741
+ this.upgrade_registration_on_cache_hit(uri, cached, options?.backward_dependencies);
23742
+ }
23096
23743
  return this.cache_entry_to_parsed_result(cached, cached.content, cached.content_hash);
23097
23744
  }
23098
23745
  let mtimeMs;
@@ -23104,7 +23751,10 @@ class ScopeResolver {
23104
23751
  if (cached && mtimeMs !== undefined && size !== undefined && cached.mtimeMs !== undefined && cached.mtimeMs === mtimeMs && cached.size !== undefined && cached.size === size) {
23105
23752
  this.cache_metrics.file.hits++;
23106
23753
  this.log(`[get_parsed_file] File cache HIT for ${uri} (mtime match, skipped read)`);
23107
- this.upgrade_registration_on_cache_hit(uri, cached, options?.backward_dependencies);
23754
+ this.file_cache.touch(cache_key);
23755
+ if (!options?.skip_backward_registration) {
23756
+ this.upgrade_registration_on_cache_hit(uri, cached, options?.backward_dependencies);
23757
+ }
23108
23758
  return this.cache_entry_to_parsed_result(cached, cached.content, cached.content_hash);
23109
23759
  }
23110
23760
  }
@@ -23122,11 +23772,14 @@ class ScopeResolver {
23122
23772
  const fallback_mtimeMs = fallback_stats?.mtimeMs;
23123
23773
  const fallback_size = fallback_stats?.size;
23124
23774
  const fallback_cache_key = this.make_file_cache_key(fallback_uri, inherited_wd);
23125
- const fallback_cached = this.file_cache.get(fallback_cache_key);
23775
+ const fallback_cached = this.file_cache.peek(fallback_cache_key);
23126
23776
  if (fallback_cached && fallback_mtimeMs !== undefined && fallback_size !== undefined && fallback_cached.mtimeMs !== undefined && fallback_cached.mtimeMs === fallback_mtimeMs && fallback_cached.size !== undefined && fallback_cached.size === fallback_size) {
23127
23777
  this.cache_metrics.file.hits++;
23128
23778
  this.log(`[get_parsed_file] File cache HIT for ${fallback_uri} (mtime match, skipped read)`);
23129
- this.upgrade_registration_on_cache_hit(fallback_uri, fallback_cached, options?.backward_dependencies);
23779
+ this.file_cache.touch(fallback_cache_key);
23780
+ if (!options?.skip_backward_registration) {
23781
+ this.upgrade_registration_on_cache_hit(fallback_uri, fallback_cached, options?.backward_dependencies);
23782
+ }
23130
23783
  return this.cache_entry_to_parsed_result(fallback_cached, fallback_cached.content, fallback_cached.content_hash);
23131
23784
  }
23132
23785
  }
@@ -23145,12 +23798,15 @@ class ScopeResolver {
23145
23798
  }
23146
23799
  }
23147
23800
  const actual_cache_key = this.make_file_cache_key(actual_uri, inherited_wd);
23148
- const actual_cached = this.file_cache.get(actual_cache_key);
23801
+ const actual_cached = this.file_cache.peek(actual_cache_key);
23149
23802
  const disk_hash = this.hash_content(content);
23150
23803
  if (actual_cached && actual_cached.content_hash === disk_hash) {
23151
23804
  this.cache_metrics.file.hits++;
23152
23805
  this.log(`[get_parsed_file] File cache HIT for ${actual_uri} (hash match)`);
23153
- this.upgrade_registration_on_cache_hit(actual_uri, actual_cached, options?.backward_dependencies);
23806
+ this.file_cache.touch(actual_cache_key);
23807
+ if (!options?.skip_backward_registration) {
23808
+ this.upgrade_registration_on_cache_hit(actual_uri, actual_cached, options?.backward_dependencies);
23809
+ }
23154
23810
  return this.cache_entry_to_parsed_result(actual_cached, content, disk_hash);
23155
23811
  } else if (actual_cached) {
23156
23812
  this.log(`[get_parsed_file] File cache STALE for ${actual_uri} (cached hash=${actual_cached.content_hash}, disk hash=${disk_hash})`);
@@ -23166,6 +23822,7 @@ class ScopeResolver {
23166
23822
  mtimeMs = stats?.mtimeMs;
23167
23823
  }
23168
23824
  this.file_cache.set(actual_cache_key, {
23825
+ uri: actual_uri,
23169
23826
  content,
23170
23827
  content_hash: disk_hash,
23171
23828
  mtimeMs,
@@ -23178,16 +23835,18 @@ class ScopeResolver {
23178
23835
  working_directory_directive: parse_result.working_directory_directive,
23179
23836
  is_standalone: parse_result.is_standalone,
23180
23837
  diagnostics: parse_result.diagnostics,
23181
- registered_backward_mode: options?.backward_dependencies ?? "explicit"
23838
+ registered_backward_mode: options?.skip_backward_registration ? undefined : options?.backward_dependencies ?? "explicit"
23182
23839
  });
23183
- this.apply_backward_directive_registration(actual_uri, parse_result.directives, { backward_dependencies: options?.backward_dependencies ?? "explicit" }, parse_result.is_standalone);
23840
+ if (!options?.skip_backward_registration) {
23841
+ this.apply_backward_directive_registration(actual_uri, parse_result.directives, { backward_dependencies: options?.backward_dependencies ?? "explicit" }, parse_result.is_standalone);
23842
+ }
23184
23843
  this.register_forward_call_relationships_from_cache(actual_uri, parse_result.forward_calls, parse_result.symbols);
23185
23844
  return { content, content_hash: disk_hash, ...parse_result };
23186
23845
  } catch (error) {
23187
23846
  this.warn(`ScopeResolver: Parse error for ${actual_uri}: ${error_message(error)}`);
23188
23847
  const empty_symbols = create_empty_symbol_table();
23189
23848
  const my_recovered = this.recover_header_facts(actual_uri, content);
23190
- if (my_recovered.recovered) {
23849
+ if (my_recovered.recovered && !options?.skip_backward_registration) {
23191
23850
  this.apply_backward_directive_registration(actual_uri, my_recovered.directives, {
23192
23851
  backward_dependencies: options?.backward_dependencies ?? "explicit"
23193
23852
  }, my_recovered.is_standalone);
@@ -23429,6 +24088,12 @@ class ScopeResolver {
23429
24088
  }
23430
24089
  };
23431
24090
  }
24091
+ get_cache_sizes() {
24092
+ return {
24093
+ scope: this.scope_cache.size,
24094
+ file: this.file_cache.size
24095
+ };
24096
+ }
23432
24097
  reset_cache_metrics() {
23433
24098
  this.cache_metrics = this.create_metrics();
23434
24099
  }
@@ -23633,14 +24298,7 @@ class ScopeResolver {
23633
24298
  }
23634
24299
  for (const my_key of keys_to_remove) {
23635
24300
  this.scope_cache.delete(my_key);
23636
- const key_uri = this.extract_uri_from_cache_key(my_key);
23637
- const key_set = this.uri_to_cache_keys.get(key_uri);
23638
- if (key_set) {
23639
- key_set.delete(my_key);
23640
- if (key_set.size === 0) {
23641
- this.uri_to_cache_keys.delete(key_uri);
23642
- }
23643
- }
24301
+ this.prune_uri_to_cache_keys_for_key(my_key);
23644
24302
  this.cache_metrics.scope.invalidations++;
23645
24303
  }
23646
24304
  }
@@ -23694,6 +24352,10 @@ class ScopeResolver {
23694
24352
  }
23695
24353
  upgrade_registration_on_cache_hit(uri, cached, requested_mode) {
23696
24354
  if (requested_mode !== "auto") {
24355
+ if (requested_mode === "explicit" && cached.registered_backward_mode === undefined) {
24356
+ this.apply_backward_directive_registration(uri, cached.directives, { backward_dependencies: "explicit" }, cached.is_standalone);
24357
+ cached.registered_backward_mode = "explicit";
24358
+ }
23697
24359
  return;
23698
24360
  }
23699
24361
  if (cached.directives.length > 0 && cached.registered_backward_mode === "auto") {
@@ -23857,7 +24519,7 @@ class ScopeResolver {
23857
24519
  this.reset_reverse_deps();
23858
24520
  }
23859
24521
  }
23860
- var DEFAULT_CONFIG2;
24522
+ var DEFAULT_CONFIG2, DEFAULT_MAX_CACHED_FILES = 2000, DEFAULT_MAX_CACHED_SCOPES = 1000;
23861
24523
  var init_scope_resolver = __esm(() => {
23862
24524
  init_esm();
23863
24525
  init_types();
@@ -23866,6 +24528,7 @@ var init_scope_resolver = __esm(() => {
23866
24528
  init_parser();
23867
24529
  init_analyzer();
23868
24530
  init_logger();
24531
+ init_lru_cache();
23869
24532
  init_dofile_locals();
23870
24533
  init_workspace_roots();
23871
24534
  init_file_path_utils();
@@ -27003,13 +27666,14 @@ class IndentationDiagnosticAnalyzer {
27003
27666
  const stataRanges = this.getStataRanges(document);
27004
27667
  const block_comment_lines2 = this.compute_block_comment_lines(lines, document.tokens);
27005
27668
  const continuation_lines = document.tokens ? this.compute_continuation_lines(document.tokens) : new Set;
27669
+ const structural_taint = this.compute_structural_taint(document);
27006
27670
  for (const range of stataRanges) {
27007
27671
  diagnostics.push(...this.find_comment_indentation_issues(lines, range, block_comment_lines2, indent_size));
27008
27672
  diagnostics.push(...this.find_block_indentation_issues(document, lines, range, block_comment_lines2, indent_size, continuation_lines));
27009
27673
  const expected_depths = this.compute_expected_depths(document, range);
27010
- diagnostics.push(...this.find_unnecessary_indentation_issues(document, lines, range, block_comment_lines2, indent_size, expected_depths, continuation_lines));
27674
+ diagnostics.push(...this.find_ast_depth_indentation_issues(lines, range, block_comment_lines2, indent_size, expected_depths, continuation_lines, structural_taint));
27011
27675
  }
27012
- return diagnostics;
27676
+ return this.dedupe_by_line_and_code(diagnostics);
27013
27677
  }
27014
27678
  compute_continuation_lines(tokens) {
27015
27679
  const continuation_lines = new Set;
@@ -27234,7 +27898,12 @@ class IndentationDiagnosticAnalyzer {
27234
27898
  const is_control_flow = node.type === "program" || node.type === "if" || node.type === "else" || node.type === "foreach" || node.type === "forvalues" || node.type === "while" || node.type === "frame";
27235
27899
  if (is_control_flow && node.body) {
27236
27900
  for (const child of node.body) {
27237
- walk_node(child, current_depth + 1);
27901
+ const child_start_line = child.range.start.line;
27902
+ if (child_start_line === start_line) {
27903
+ walk_node(child, current_depth);
27904
+ } else {
27905
+ walk_node(child, current_depth + 1);
27906
+ }
27238
27907
  }
27239
27908
  const end_line = node.range.end.line;
27240
27909
  if (end_line !== start_line && end_line >= range.start && end_line <= range.end) {
@@ -27277,7 +27946,7 @@ class IndentationDiagnosticAnalyzer {
27277
27946
  const block_node = node;
27278
27947
  for (const my_child of block_node.body) {
27279
27948
  const child_start_line = my_child.range.start.line;
27280
- if (child_start_line === start_line && this.is_block_node_type(my_child)) {
27949
+ if (child_start_line === start_line) {
27281
27950
  walk_node(my_child, depth);
27282
27951
  } else {
27283
27952
  walk_node(my_child, depth + 1);
@@ -27319,14 +27988,15 @@ class IndentationDiagnosticAnalyzer {
27319
27988
  }
27320
27989
  return false;
27321
27990
  }
27322
- find_unnecessary_indentation_issues(document, lines, range, block_comment_lines2, indent_size, expected_depths, continuation_lines) {
27991
+ find_ast_depth_indentation_issues(lines, range, block_comment_lines2, indent_size, expected_depths, continuation_lines, structural_taint) {
27323
27992
  const diagnostics = [];
27324
27993
  for (let i = range.start;i <= range.end && i < lines.length; i++) {
27325
27994
  const line = lines[i];
27326
- if (this.should_skip_unnecessary_check(line, i, block_comment_lines2, continuation_lines)) {
27995
+ if (this.should_skip_unnecessary_check(line, i, block_comment_lines2, continuation_lines) || structural_taint.has(i)) {
27327
27996
  continue;
27328
27997
  }
27329
27998
  const actual_indent = this.get_line_indentation(line, indent_size);
27999
+ const has_known_depth = expected_depths.has(i);
27330
28000
  const expected_depth = expected_depths.get(i) ?? 0;
27331
28001
  const expected_indent = expected_depth * indent_size;
27332
28002
  if (actual_indent > expected_indent) {
@@ -27338,12 +28008,94 @@ class IndentationDiagnosticAnalyzer {
27338
28008
  code: "UNNECESSARY_INDENTATION" /* UNNECESSARY_INDENTATION */,
27339
28009
  ...diagnostic_code_description_fields("UNNECESSARY_INDENTATION" /* UNNECESSARY_INDENTATION */)
27340
28010
  });
28011
+ } else if (has_known_depth && actual_indent < expected_indent) {
28012
+ diagnostics.push({
28013
+ severity: import_node.DiagnosticSeverity.Information,
28014
+ range: import_node.Range.create(import_node.Position.create(i, 0), import_node.Position.create(i, actual_indent)),
28015
+ message: "Line appears under-indented for its block depth. Use Format Document to fix.",
28016
+ source: "sight",
28017
+ code: "MISSING_INDENTATION" /* MISSING_INDENTATION */,
28018
+ ...diagnostic_code_description_fields("MISSING_INDENTATION" /* MISSING_INDENTATION */)
28019
+ });
27341
28020
  }
27342
28021
  }
27343
28022
  return diagnostics;
27344
28023
  }
28024
+ compute_structural_taint(document) {
28025
+ const tainted_lines = new Set;
28026
+ const the_error_lines = [];
28027
+ for (const my_diagnostic of document.diagnostics) {
28028
+ const my_code = typeof my_diagnostic.code === "string" ? my_diagnostic.code : "";
28029
+ if (STRUCTURAL_NESTING_ERROR_CODES.has(my_code)) {
28030
+ the_error_lines.push(my_diagnostic.range.start.line);
28031
+ }
28032
+ }
28033
+ if (the_error_lines.length === 0) {
28034
+ return tainted_lines;
28035
+ }
28036
+ if (!document.ast) {
28037
+ for (const my_error_line of the_error_lines) {
28038
+ tainted_lines.add(my_error_line);
28039
+ }
28040
+ return tainted_lines;
28041
+ }
28042
+ for (const my_error_line of the_error_lines) {
28043
+ const my_block = this.find_smallest_block_containing(document.ast, my_error_line);
28044
+ if (my_block) {
28045
+ for (let my_line = my_block.range.start.line;my_line <= my_block.range.end.line; my_line++) {
28046
+ tainted_lines.add(my_line);
28047
+ }
28048
+ } else {
28049
+ tainted_lines.add(my_error_line);
28050
+ }
28051
+ }
28052
+ return tainted_lines;
28053
+ }
28054
+ find_smallest_block_containing(ast, line) {
28055
+ let best = null;
28056
+ const walk = (node) => {
28057
+ const start_line = node.range.start.line;
28058
+ const end_line = node.range.end.line;
28059
+ if (line < start_line || line > end_line) {
28060
+ return;
28061
+ }
28062
+ if (this.is_indentation_block_node(node)) {
28063
+ if (best === null || end_line - start_line <= best.range.end.line - best.range.start.line) {
28064
+ best = node;
28065
+ }
28066
+ }
28067
+ if ("body" in node && Array.isArray(node.body)) {
28068
+ for (const my_child of node.body) {
28069
+ walk(my_child);
28070
+ }
28071
+ }
28072
+ };
28073
+ for (const my_node of ast.nodes) {
28074
+ walk(my_node);
28075
+ }
28076
+ return best;
28077
+ }
28078
+ is_indentation_block_node(node) {
28079
+ if (this.is_block_node_type(node)) {
28080
+ return true;
28081
+ }
28082
+ return node.type === "command" && "name" in node && node.name === "{";
28083
+ }
28084
+ dedupe_by_line_and_code(diagnostics) {
28085
+ const seen_line_codes = new Set;
28086
+ const deduped = [];
28087
+ for (const my_diagnostic of diagnostics) {
28088
+ const my_key = `${my_diagnostic.range.start.line}:${my_diagnostic.code}`;
28089
+ if (seen_line_codes.has(my_key)) {
28090
+ continue;
28091
+ }
28092
+ seen_line_codes.add(my_key);
28093
+ deduped.push(my_diagnostic);
28094
+ }
28095
+ return deduped;
28096
+ }
27345
28097
  }
27346
- var import_node, CONTROL_FLOW_RE, FIRST_WORD_RE;
28098
+ var import_node, CONTROL_FLOW_RE, FIRST_WORD_RE, STRUCTURAL_NESTING_ERROR_CODES;
27347
28099
  var init_indentation_diagnostics = __esm(() => {
27348
28100
  init_types2();
27349
28101
  init_types();
@@ -27351,6 +28103,17 @@ var init_indentation_diagnostics = __esm(() => {
27351
28103
  import_node = __toESM(require_main4(), 1);
27352
28104
  CONTROL_FLOW_RE = /^(if|foreach|while|program|mata|python)\b/;
27353
28105
  FIRST_WORD_RE = /^(\w+)\b/;
28106
+ STRUCTURAL_NESTING_ERROR_CODES = new Set([
28107
+ "OPEN_BRACE_ALONE",
28108
+ "ORPHAN_CLOSE_BRACE",
28109
+ "BRACE_NOT_ALONE",
28110
+ "BRACE_ELSE_SAME_LINE",
28111
+ "CODE_AFTER_OPEN_BRACE",
28112
+ "UNCLOSED_BLOCK",
28113
+ "MISSING_PROGRAM_END",
28114
+ "UNBALANCED_QUOTES",
28115
+ "UNBALANCED_BLOCK_COMMENT"
28116
+ ]);
27354
28117
  });
27355
28118
 
27356
28119
  // src/utils/diagnostic-severity.ts
@@ -29930,137 +30693,6 @@ var init_command_database = __esm(() => {
29930
30693
  command_database = new CommandDatabase;
29931
30694
  });
29932
30695
 
29933
- // src/utils/lru-cache.ts
29934
- class LRUCache {
29935
- cache = new Map;
29936
- max_size;
29937
- hits = 0;
29938
- misses = 0;
29939
- evictions = 0;
29940
- current_generation = 0;
29941
- constructor(max_size = 100) {
29942
- this.max_size = max_size;
29943
- }
29944
- get(key) {
29945
- const entry = this.cache.get(key);
29946
- if (entry !== undefined) {
29947
- this.hits++;
29948
- entry.access_count++;
29949
- this.cache.delete(key);
29950
- this.cache.set(key, entry);
29951
- return entry.value;
29952
- } else {
29953
- this.misses++;
29954
- }
29955
- return;
29956
- }
29957
- set(key, value) {
29958
- this.cache.delete(key);
29959
- if (this.cache.size >= this.max_size) {
29960
- this.evict_one();
29961
- }
29962
- this.cache.set(key, {
29963
- value,
29964
- generation: this.current_generation,
29965
- access_count: 1
29966
- });
29967
- }
29968
- evict_one() {
29969
- let victim_key;
29970
- let victim_score = Infinity;
29971
- for (const [key, entry] of this.cache) {
29972
- const score = entry.generation * 1000 + entry.access_count;
29973
- if (score < victim_score) {
29974
- victim_score = score;
29975
- victim_key = key;
29976
- }
29977
- }
29978
- if (victim_key !== undefined) {
29979
- this.cache.delete(victim_key);
29980
- this.evictions++;
29981
- }
29982
- }
29983
- advance_generation() {
29984
- this.current_generation++;
29985
- }
29986
- trim_to_top_n(n) {
29987
- if (this.cache.size <= n)
29988
- return;
29989
- const entries = Array.from(this.cache.entries()).sort((a, b) => b[1].access_count - a[1].access_count);
29990
- this.cache.clear();
29991
- for (let i = 0;i < Math.min(n, entries.length); i++) {
29992
- this.cache.set(entries[i][0], entries[i][1]);
29993
- }
29994
- this.evictions += entries.length - n;
29995
- }
29996
- has(key) {
29997
- return this.cache.has(key);
29998
- }
29999
- clear() {
30000
- this.cache.clear();
30001
- }
30002
- size() {
30003
- return this.cache.size;
30004
- }
30005
- get_stats() {
30006
- return { hits: this.hits, misses: this.misses, evictions: this.evictions };
30007
- }
30008
- }
30009
-
30010
- class CompletionPrefixCache {
30011
- cache;
30012
- command_db_version = 0;
30013
- workspace_version = 0;
30014
- top_n_threshold;
30015
- constructor(max_size = 100, top_n_threshold = 200) {
30016
- this.cache = new LRUCache(max_size);
30017
- this.top_n_threshold = top_n_threshold;
30018
- }
30019
- make_key(prefix, context, document_version = 0) {
30020
- return `${context}:${prefix}:${this.command_db_version}:${this.workspace_version}:${document_version}`;
30021
- }
30022
- get_with_context(prefix, context, document_version = 0) {
30023
- return this.cache.get(this.make_key(prefix, context, document_version));
30024
- }
30025
- set_with_context(prefix, context, value, document_version = 0) {
30026
- const trimmed_value = this.apply_limit(value);
30027
- this.cache.set(this.make_key(prefix, context, document_version), trimmed_value);
30028
- return trimmed_value;
30029
- }
30030
- apply_limit(value) {
30031
- if (this.top_n_threshold > 0 && value.length > this.top_n_threshold) {
30032
- return value.slice(0, this.top_n_threshold);
30033
- }
30034
- return value;
30035
- }
30036
- has_with_context(prefix, context, document_version = 0) {
30037
- return this.cache.has(this.make_key(prefix, context, document_version));
30038
- }
30039
- invalidate_on_db_change(new_version) {
30040
- if (new_version !== this.command_db_version) {
30041
- this.command_db_version = new_version;
30042
- this.cache.clear();
30043
- this.cache.advance_generation();
30044
- }
30045
- }
30046
- invalidate_on_workspace_change(new_version) {
30047
- if (new_version !== this.workspace_version) {
30048
- this.workspace_version = new_version;
30049
- this.cache.clear();
30050
- this.cache.advance_generation();
30051
- }
30052
- }
30053
- clear() {
30054
- this.cache.clear();
30055
- }
30056
- size() {
30057
- return this.cache.size();
30058
- }
30059
- get_stats() {
30060
- return this.cache.get_stats();
30061
- }
30062
- }
30063
-
30064
30696
  // src/utils/symbol-index-cache.ts
30065
30697
  class SymbolIndexCache {
30066
30698
  filtered_views = new Map;
@@ -30429,12 +31061,58 @@ function compute_ranking_key(factors) {
30429
31061
  const name = factors.alphabetical_order.toLowerCase();
30430
31062
  return `${scope_priority}${directive_priority}${symbol_priority_padded}|${parent_uri}|${name}`;
30431
31063
  }
31064
+ function stata_region_floor_line(document, position) {
31065
+ const the_ranges = document.context_ranges;
31066
+ if (!the_ranges || the_ranges.length === 0) {
31067
+ return 0;
31068
+ }
31069
+ let floor_line = 0;
31070
+ for (const my_range of the_ranges) {
31071
+ if (my_range.context === "stata" /* STATA */) {
31072
+ continue;
31073
+ }
31074
+ if (my_range.start_delimiter?.command?.endsWith(":")) {
31075
+ continue;
31076
+ }
31077
+ if (my_range.range.end.line < position.line) {
31078
+ floor_line = Math.max(floor_line, my_range.range.end.line + 1);
31079
+ }
31080
+ }
31081
+ return floor_line;
31082
+ }
31083
+ function build_logical_text_before_cursor(document, position, tokens, start, physical_text_before_cursor) {
31084
+ if (start === undefined || tokens === undefined) {
31085
+ return physical_text_before_cursor;
31086
+ }
31087
+ const current_line = get_line_text(document, position.line);
31088
+ const physical_current = current_line.substring(start.line === position.line ? start.character : 0, position.character);
31089
+ if (start.line === position.line) {
31090
+ return physical_current;
31091
+ }
31092
+ const the_parts = [];
31093
+ for (let my_i = start.index;my_i < tokens.length && tokens[my_i].range.start.line < position.line; my_i++) {
31094
+ const my_token = tokens[my_i];
31095
+ if (LOGICAL_FLATTEN_SKIP_TYPES.has(my_token.type)) {
31096
+ continue;
31097
+ }
31098
+ the_parts.push(my_token.value);
31099
+ }
31100
+ const prior_text = the_parts.join(" ");
31101
+ return prior_text === "" ? physical_current : prior_text + " " + physical_current;
31102
+ }
30432
31103
  function detect_completion_context(document, position, tokens, command_db) {
30433
31104
  if (position.line >= get_line_count(document)) {
30434
31105
  return { type: "fallback" };
30435
31106
  }
30436
31107
  const current_line = get_line_text(document, position.line);
30437
31108
  const text_before_cursor = current_line.substring(0, position.character);
31109
+ let cached_logical_text;
31110
+ const get_logical_text = () => {
31111
+ if (cached_logical_text === undefined) {
31112
+ cached_logical_text = build_logical_text_before_cursor(document, position, tokens, logical_statement_start(tokens, position, stata_region_floor_line(document, position)), text_before_cursor);
31113
+ }
31114
+ return cached_logical_text;
31115
+ };
30438
31116
  const extended_macro_context = detect_extended_macro_context(text_before_cursor, position);
30439
31117
  if (extended_macro_context) {
30440
31118
  return extended_macro_context;
@@ -30443,7 +31121,7 @@ function detect_completion_context(document, position, tokens, command_db) {
30443
31121
  if (directive_context && !is_cursor_in_block_comment(document, position)) {
30444
31122
  return directive_context;
30445
31123
  }
30446
- const command_path_context = detect_command_path_context(text_before_cursor);
31124
+ const command_path_context = detect_command_path_context(get_logical_text());
30447
31125
  if (command_path_context) {
30448
31126
  return command_path_context;
30449
31127
  }
@@ -30451,18 +31129,18 @@ function detect_completion_context(document, position, tokens, command_db) {
30451
31129
  if (macro_context) {
30452
31130
  return macro_context;
30453
31131
  }
30454
- const option_context = detect_option_context(text_before_cursor, document, position);
31132
+ const option_context = detect_option_context(get_logical_text(), document, position);
30455
31133
  if (option_context) {
30456
31134
  return option_context;
30457
31135
  }
30458
- const subcommand_context = detect_subcommand_context(text_before_cursor, command_db);
31136
+ const subcommand_context = detect_subcommand_context(get_logical_text(), command_db);
30459
31137
  if (subcommand_context) {
30460
31138
  return subcommand_context;
30461
31139
  }
30462
- if (is_command_context(text_before_cursor)) {
31140
+ if (is_command_context(get_logical_text())) {
30463
31141
  return { type: "command" };
30464
31142
  }
30465
- if (is_variable_context(text_before_cursor)) {
31143
+ if (is_variable_context(get_logical_text())) {
30466
31144
  return { type: "variable" };
30467
31145
  }
30468
31146
  return { type: "fallback" };
@@ -30575,6 +31253,9 @@ function extract_command_name(text) {
30575
31253
  }
30576
31254
  const the_words = working_text.split(/\s+/).filter((w) => w.length > 0);
30577
31255
  for (const my_word of the_words) {
31256
+ if (my_word === ":") {
31257
+ continue;
31258
+ }
30578
31259
  const lower_word = my_word.toLowerCase();
30579
31260
  if (!PREFIX_COMMANDS2.includes(lower_word)) {
30580
31261
  return my_word;
@@ -30866,7 +31547,7 @@ class CompletionProvider {
30866
31547
  if (my_current_context === "stata" /* STATA */ && is_cursor_in_block_comment(document, position, { require_tokens: true })) {
30867
31548
  return [];
30868
31549
  }
30869
- const context = detect_completion_context(document, position, undefined, this.command_db);
31550
+ const context = detect_completion_context(document, position, my_current_context === "stata" /* STATA */ ? document.tokens : undefined, this.command_db);
30870
31551
  if (my_current_context !== "stata" /* STATA */ && context.type !== "macro") {
30871
31552
  const my_boundary_completions = this.get_block_boundary_completions(document, position);
30872
31553
  return my_boundary_completions;
@@ -31202,7 +31883,8 @@ class CompletionProvider {
31202
31883
  return "";
31203
31884
  }
31204
31885
  const current_line = get_line_text(document, position.line);
31205
- const text_before_cursor = current_line.substring(0, position.character);
31886
+ const physical_text_before_cursor = current_line.substring(0, position.character);
31887
+ const text_before_cursor = build_logical_text_before_cursor(document, position, document.tokens, logical_statement_start(document.tokens, position, stata_region_floor_line(document, position)), physical_text_before_cursor);
31206
31888
  let paren_depth = 0;
31207
31889
  let in_string = false;
31208
31890
  let last_comma_pos = -1;
@@ -32216,12 +32898,13 @@ class CompletionProvider {
32216
32898
  return position.line >= body_start && position.line < program.range.end.line;
32217
32899
  }
32218
32900
  }
32219
- var import_vscode_languageserver4, DIRECTIVE_PATH_CONTEXT_PATTERN;
32901
+ var import_vscode_languageserver4, DIRECTIVE_PATH_CONTEXT_PATTERN, LOGICAL_FLATTEN_SKIP_TYPES;
32220
32902
  var init_completion = __esm(() => {
32221
32903
  init_command_database();
32222
32904
  init_scoped_locals();
32223
32905
  init_dofile_locals();
32224
32906
  init_types2();
32907
+ init_lru_cache();
32225
32908
  init_scope_resolver();
32226
32909
  init_scope_resolver();
32227
32910
  init_analyzer();
@@ -32230,8 +32913,18 @@ var init_completion = __esm(() => {
32230
32913
  init_comment_utils();
32231
32914
  init_logger();
32232
32915
  init_macro_completion();
32916
+ init_statement_span();
32233
32917
  import_vscode_languageserver4 = __toESM(require_main4(), 1);
32234
32918
  DIRECTIVE_PATH_CONTEXT_PATTERN = new RegExp(`^\\s*(${DIRECTIVE_BODY_PREFIX_PATTERN}[a-zA-Z-]+)\\s*:?\\s*(.*)$`);
32919
+ LOGICAL_FLATTEN_SKIP_TYPES = new Set([
32920
+ "WHITESPACE",
32921
+ "COMMENT_LINE",
32922
+ "COMMENT_BLOCK",
32923
+ "CONTINUATION",
32924
+ "STATEMENT_TERMINATOR",
32925
+ "DELIMIT_DIRECTIVE",
32926
+ "EOF"
32927
+ ]);
32235
32928
  });
32236
32929
 
32237
32930
  // src/utils/marker-scanner.ts
@@ -32495,7 +33188,8 @@ function create_completion_handler(deps) {
32495
33188
  const config = await deps.get_document_settings(params.textDocument.uri);
32496
33189
  const graph_version = deps.dependency_graph ? deps.dependency_graph.get_version() : undefined;
32497
33190
  const items = await deps.completion_provider.get_completions(document_state, params.position, trigger_character, deps.scope_resolver || undefined, workspace_symbols, scope_resolver_config_for(config), workspace_version, token, graph_version);
32498
- const completion_context = detect_completion_context(document_state, params.position, document_state.tokens);
33191
+ const probe_context = document_state.context_tracker ? document_state.context_tracker.get_context_at_position(params.position) : "stata" /* STATA */;
33192
+ const completion_context = detect_completion_context(document_state, params.position, probe_context === "stata" /* STATA */ ? document_state.tokens : undefined);
32499
33193
  const is_macro_context = completion_context.type === "macro";
32500
33194
  return { isIncomplete: is_macro_context, items };
32501
33195
  }
@@ -32853,6 +33547,7 @@ var import_node7, DEFAULT_SETTINGS, MAX_IHLP_CACHE_SIZE = 500, MAX_RESOLVE_STHLP
32853
33547
  var init_server_handlers = __esm(() => {
32854
33548
  init_completion();
32855
33549
  init_context_tracker();
33550
+ init_types2();
32856
33551
  init_scope_resolver();
32857
33552
  init_include_expander();
32858
33553
  init_marker_scanner();
@@ -32865,7 +33560,7 @@ var init_server_handlers = __esm(() => {
32865
33560
  severity: {
32866
33561
  undefinedMacro: "warning",
32867
33562
  undefinedVariable: "off",
32868
- styleWarnings: "hint",
33563
+ styleWarnings: "information",
32869
33564
  malformedOperator: "warning",
32870
33565
  spacedCompoundOperator: "information",
32871
33566
  invalidOperatorSequence: "error",
@@ -32906,6 +33601,9 @@ var init_server_handlers = __esm(() => {
32906
33601
  max_forward_depth: 10,
32907
33602
  max_chain_depth: 20,
32908
33603
  max_callee_revalidations: 10,
33604
+ max_cached_files: 2000,
33605
+ max_cached_scopes: 1000,
33606
+ max_cached_forward_closures: 2000,
32909
33607
  diagnostics: {
32910
33608
  missing_file: "warning",
32911
33609
  max_depth: "information",
@@ -33084,6 +33782,15 @@ function validate_comment_formatting_config(config, log_warning) {
33084
33782
  if (typeof cross_file.max_callee_revalidations === "number" && cross_file.max_callee_revalidations > 0) {
33085
33783
  validated_config.cross_file.max_callee_revalidations = cross_file.max_callee_revalidations;
33086
33784
  }
33785
+ if (typeof cross_file.max_cached_files === "number" && cross_file.max_cached_files > 0) {
33786
+ validated_config.cross_file.max_cached_files = cross_file.max_cached_files;
33787
+ }
33788
+ if (typeof cross_file.max_cached_scopes === "number" && cross_file.max_cached_scopes > 0) {
33789
+ validated_config.cross_file.max_cached_scopes = cross_file.max_cached_scopes;
33790
+ }
33791
+ if (typeof cross_file.max_cached_forward_closures === "number" && cross_file.max_cached_forward_closures > 0) {
33792
+ validated_config.cross_file.max_cached_forward_closures = cross_file.max_cached_forward_closures;
33793
+ }
33087
33794
  if (cross_file.diagnostics) {
33088
33795
  const valid_severities = ["error", "warning", "information", "info", "off"];
33089
33796
  const valid_case_mismatch_severities = [
@@ -33766,10 +34473,16 @@ async function build_check_context(workspace_root, config) {
33766
34473
  }
33767
34474
  },
33768
34475
  warn: (message) => console.error(message)
34476
+ }, undefined, {
34477
+ max_cached_files: config.cross_file.max_cached_files,
34478
+ max_cached_scopes: config.cross_file.max_cached_scopes
33769
34479
  });
33770
34480
  const forward_scope_resolver = new ForwardScopeResolver(scope_resolver, {
33771
34481
  max_forward_depth: config.cross_file.max_forward_depth
33772
34482
  });
34483
+ if (config.cross_file.max_cached_forward_closures !== undefined) {
34484
+ forward_scope_resolver.set_forward_closure_memo_capacity(config.cross_file.max_cached_forward_closures);
34485
+ }
33773
34486
  const diagnostics_provider = new DiagnosticsProvider({
33774
34487
  sendDiagnostics: () => {
33775
34488
  return;
@@ -34235,57 +34948,6 @@ var init_main = __esm(() => {
34235
34948
  })(TextDocument || (TextDocument = {}));
34236
34949
  });
34237
34950
 
34238
- // src/utils/token-utils.ts
34239
- function position_before2(a, b) {
34240
- return a.line < b.line || a.line === b.line && a.character < b.character;
34241
- }
34242
- function position_at_or_before(a, b) {
34243
- return a.line < b.line || a.line === b.line && a.character <= b.character;
34244
- }
34245
- function find_last_token_starting_at_or_before(tokens, position) {
34246
- let low = 0;
34247
- let high = tokens.length - 1;
34248
- let best = -1;
34249
- while (low <= high) {
34250
- const mid = low + high >>> 1;
34251
- const start = tokens[mid].range.start;
34252
- if (position_at_or_before(start, position)) {
34253
- best = mid;
34254
- low = mid + 1;
34255
- } else {
34256
- high = mid - 1;
34257
- }
34258
- }
34259
- return best;
34260
- }
34261
- function find_last_token_starting_before(tokens, position) {
34262
- let low = 0;
34263
- let high = tokens.length - 1;
34264
- let best = -1;
34265
- while (low <= high) {
34266
- const mid = low + high >>> 1;
34267
- const start = tokens[mid].range.start;
34268
- if (position_before2(start, position)) {
34269
- best = mid;
34270
- low = mid + 1;
34271
- } else {
34272
- high = mid - 1;
34273
- }
34274
- }
34275
- return best;
34276
- }
34277
- function find_token_index_at_position(tokens, position) {
34278
- const candidate = find_last_token_starting_at_or_before(tokens, position);
34279
- if (candidate === -1) {
34280
- return -1;
34281
- }
34282
- const end = tokens[candidate].range.end;
34283
- if (position_before2(position, end)) {
34284
- return candidate;
34285
- }
34286
- return -1;
34287
- }
34288
-
34289
34951
  // src/utils/string-literal-utils.ts
34290
34952
  function is_cursor_in_string_literal(document, position) {
34291
34953
  if (!document.tokens || document.tokens.length === 0) {
@@ -38481,27 +39143,20 @@ class IndentationAnalyzer {
38481
39143
  if (!this.has_trivia(node))
38482
39144
  return;
38483
39145
  const node_with_trivia = node;
38484
- if (node_with_trivia.leadingTrivia) {
38485
- for (const my_trivia of node_with_trivia.leadingTrivia) {
38486
- if (my_trivia.type === "comment") {
38487
- const trivia_line = my_trivia.range.start.line;
38488
- if (!this.indentation_map.has(trivia_line)) {
38489
- const { delta, original_indent } = this.calculate_indent_delta(trivia_line, this.current_depth);
38490
- this.set_indentation(trivia_line, this.current_depth, false, false, false, false, delta, original_indent);
38491
- }
38492
- }
38493
- }
38494
- }
38495
- if (node_with_trivia.trailingTrivia) {
38496
- for (const my_trivia of node_with_trivia.trailingTrivia) {
38497
- if (my_trivia.type === "comment") {
38498
- const trivia_line = my_trivia.range.start.line;
38499
- if (!this.indentation_map.has(trivia_line)) {
38500
- const { delta, original_indent } = this.calculate_indent_delta(trivia_line, this.current_depth);
38501
- this.set_indentation(trivia_line, this.current_depth, false, false, false, false, delta, original_indent);
38502
- }
38503
- }
38504
- }
39146
+ this.mark_comment_trivia_indentation(node_with_trivia.leadingTrivia);
39147
+ this.mark_comment_trivia_indentation(node_with_trivia.trailingTrivia);
39148
+ }
39149
+ mark_comment_trivia_indentation(the_trivia) {
39150
+ if (!the_trivia)
39151
+ return;
39152
+ for (const my_trivia of the_trivia) {
39153
+ if (my_trivia.type !== "comment")
39154
+ continue;
39155
+ const trivia_line = my_trivia.range.start.line;
39156
+ if (this.indentation_map.has(trivia_line))
39157
+ continue;
39158
+ const { delta, original_indent } = this.calculate_indent_delta(trivia_line, this.current_depth);
39159
+ this.set_indentation(trivia_line, this.current_depth, false, false, false, false, delta, original_indent);
38505
39160
  }
38506
39161
  }
38507
39162
  is_block_node(node) {
@@ -38524,6 +39179,7 @@ class IndentationAnalyzer {
38524
39179
  this.walk_node(my_child);
38525
39180
  }
38526
39181
  }
39182
+ this.process_block_ending_trivia(node);
38527
39183
  this.current_depth--;
38528
39184
  if (end_line !== start_line) {
38529
39185
  const end_delta_info = this.calculate_indent_delta(end_line, this.current_depth);
@@ -38556,6 +39212,7 @@ class IndentationAnalyzer {
38556
39212
  this.set_indentation(line, this.current_depth, false, false, false, false, delta, original_indent);
38557
39213
  }
38558
39214
  }
39215
+ this.process_block_ending_trivia(node);
38559
39216
  this.current_depth--;
38560
39217
  if (end_line !== start_line) {
38561
39218
  const end_delta_info = this.calculate_indent_delta(end_line, this.current_depth);
@@ -38567,6 +39224,10 @@ class IndentationAnalyzer {
38567
39224
  const { delta, original_indent } = this.calculate_indent_delta(start_line, this.current_depth);
38568
39225
  this.set_indentation(start_line, this.current_depth, false, false, false, false, delta, original_indent);
38569
39226
  }
39227
+ process_block_ending_trivia(node) {
39228
+ const node_with_trivia = node;
39229
+ this.mark_comment_trivia_indentation(node_with_trivia.blockEndingTrivia);
39230
+ }
38570
39231
  process_continuations(tokens) {
38571
39232
  let continuation_indent = 0;
38572
39233
  let in_continuation = false;
@@ -40070,6 +40731,7 @@ class PrettyPrinter {
40070
40731
  for (const my_stmt of node.body) {
40071
40732
  the_parts.push(this.printNode(my_stmt));
40072
40733
  }
40734
+ the_parts.push(this.printBlockEndingTrivia(node));
40073
40735
  this.current_indent--;
40074
40736
  the_parts.push(`${this.getIndent()}}`);
40075
40737
  }
@@ -40090,7 +40752,9 @@ class PrettyPrinter {
40090
40752
  printOption(option) {
40091
40753
  let result = option.name;
40092
40754
  if (option.argument !== undefined) {
40093
- result += `(${format_expression_spacing(option.argument)})`;
40755
+ const close_paren = option.argument_unclosed ? "" : ")";
40756
+ const argument = format_expression_spacing(option.argument);
40757
+ result += `(${argument}${close_paren}`;
40094
40758
  }
40095
40759
  return result;
40096
40760
  }
@@ -40102,6 +40766,7 @@ class PrettyPrinter {
40102
40766
  for (const my_stmt of node.body) {
40103
40767
  the_lines.push(this.printNode(my_stmt));
40104
40768
  }
40769
+ the_lines.push(this.printBlockEndingTrivia(node));
40105
40770
  this.current_indent--;
40106
40771
  the_lines.push(`${this.getIndent()}end`);
40107
40772
  return the_lines.join("");
@@ -40160,6 +40825,7 @@ class PrettyPrinter {
40160
40825
  for (const my_stmt of node.body) {
40161
40826
  the_lines.push(this.printNode(my_stmt));
40162
40827
  }
40828
+ the_lines.push(this.printBlockEndingTrivia(node));
40163
40829
  this.current_indent--;
40164
40830
  the_lines.push(`${this.getIndent()}}`);
40165
40831
  return the_lines.join("");
@@ -40220,6 +40886,19 @@ class PrettyPrinter {
40220
40886
  }
40221
40887
  return the_parts.join("");
40222
40888
  }
40889
+ printBlockEndingTrivia(node) {
40890
+ if (!node.blockEndingTrivia || node.blockEndingTrivia.length === 0) {
40891
+ return "";
40892
+ }
40893
+ const the_parts = [];
40894
+ for (const my_trivia of node.blockEndingTrivia) {
40895
+ the_parts.push(this.getIndent());
40896
+ the_parts.push(this.printTrivia(my_trivia));
40897
+ the_parts.push(`
40898
+ `);
40899
+ }
40900
+ return the_parts.join("");
40901
+ }
40223
40902
  printTrivia(trivia) {
40224
40903
  return trivia.content;
40225
40904
  }
@@ -40315,28 +40994,17 @@ class CodeFormatter {
40315
40994
  const the_embedded_blocks = new Map;
40316
40995
  let my_placeholder_counter = 0;
40317
40996
  let my_modified_content = document.content;
40318
- const the_sorted_ranges = [...context_ranges].sort((a, b) => b.range.start.line - a.range.start.line);
40319
- for (const my_range of the_sorted_ranges) {
40997
+ const the_replacement_units = this.coalesce_embedded_ranges(the_doc, context_ranges);
40998
+ for (const my_unit of the_replacement_units) {
40320
40999
  if (my_placeholder_counter >= MAX_EMBEDDED_BLOCKS) {
40321
41000
  break;
40322
41001
  }
40323
41002
  const my_placeholder = `__EMBEDDED_BLOCK_${my_placeholder_counter}__`;
40324
41003
  the_embedded_blocks.set(my_placeholder, {
40325
- content: this.extract_block_content(the_doc, my_range),
40326
- range: my_range
41004
+ content: my_unit.content,
41005
+ range: my_unit.range
40327
41006
  });
40328
- let actual_range;
40329
- if (my_range.is_single_line) {
40330
- actual_range = my_range.range;
40331
- } else {
40332
- const actual_end_line = my_range.end_delimiter ? my_range.end_delimiter.range.start.line : my_range.range.end.line;
40333
- const end_line_text = get_line_text(the_doc, actual_end_line);
40334
- actual_range = {
40335
- start: my_range.range.start,
40336
- end: { line: actual_end_line, character: end_line_text.length }
40337
- };
40338
- }
40339
- my_modified_content = this.replace_range_in_content(my_modified_content, actual_range, my_placeholder);
41007
+ my_modified_content = this.replace_range_in_content(my_modified_content, my_unit.actual_range, my_placeholder);
40340
41008
  my_placeholder_counter++;
40341
41009
  }
40342
41010
  const indent_size = options.tabSize ?? server_config?.formatting?.indentSize ?? 4;
@@ -40366,7 +41034,7 @@ class CodeFormatter {
40366
41034
  const indented_block_lines = block_lines.map((line, index) => {
40367
41035
  if (index === 0) {
40368
41036
  return leading_indent + line;
40369
- } else if (index === block_lines.length - 1 && line.trim() === expected_end_delimiter) {
41037
+ } else if (!my_block_info.range.is_single_line && index === block_lines.length - 1 && line.trim() === expected_end_delimiter) {
40370
41038
  return leading_indent + line;
40371
41039
  } else {
40372
41040
  return line;
@@ -40391,6 +41059,90 @@ class CodeFormatter {
40391
41059
  newText: this.strip_trailing_whitespace(my_formatted_content)
40392
41060
  }];
40393
41061
  }
41062
+ embedded_range_span(doc, context_range) {
41063
+ if (context_range.is_single_line) {
41064
+ return {
41065
+ start_line: context_range.range.start.line,
41066
+ end_line: context_range.range.end.line
41067
+ };
41068
+ }
41069
+ const my_end_line = context_range.end_delimiter ? context_range.end_delimiter.range.start.line : context_range.range.end.line;
41070
+ return { start_line: context_range.range.start.line, end_line: my_end_line };
41071
+ }
41072
+ coalesce_embedded_ranges(doc, context_ranges) {
41073
+ const the_spans = context_ranges.map((my_range) => ({
41074
+ my_range,
41075
+ span: this.embedded_range_span(doc, my_range)
41076
+ })).sort((a, b) => {
41077
+ if (a.span.start_line !== b.span.start_line) {
41078
+ return a.span.start_line - b.span.start_line;
41079
+ }
41080
+ return a.span.end_line - b.span.end_line;
41081
+ });
41082
+ const the_groups = [];
41083
+ for (const my_item of the_spans) {
41084
+ const my_last = the_groups[the_groups.length - 1];
41085
+ if (my_last && my_item.span.start_line <= my_last.end_line) {
41086
+ my_last.end_line = Math.max(my_last.end_line, my_item.span.end_line);
41087
+ my_last.members.push(my_item.my_range);
41088
+ } else {
41089
+ the_groups.push({
41090
+ start_line: my_item.span.start_line,
41091
+ end_line: my_item.span.end_line,
41092
+ members: [my_item.my_range]
41093
+ });
41094
+ }
41095
+ }
41096
+ const the_line_count = get_line_count(doc);
41097
+ const the_units = [];
41098
+ for (const my_group of the_groups) {
41099
+ if (my_group.members.length === 1) {
41100
+ const my_range = my_group.members[0];
41101
+ const my_end_line_text2 = get_line_text(doc, my_group.end_line);
41102
+ the_units.push({
41103
+ content: this.extract_block_content(doc, my_range),
41104
+ range: my_range,
41105
+ actual_range: {
41106
+ start: { line: my_group.start_line, character: 0 },
41107
+ end: {
41108
+ line: my_group.end_line,
41109
+ character: my_end_line_text2.length
41110
+ }
41111
+ }
41112
+ });
41113
+ continue;
41114
+ }
41115
+ const my_lines = [];
41116
+ for (let my_line = my_group.start_line;my_line <= my_group.end_line && my_line < the_line_count; my_line++) {
41117
+ const my_text = get_line_text(doc, my_line);
41118
+ my_lines.push(my_line === my_group.start_line ? my_text.trimStart() : my_text);
41119
+ }
41120
+ const my_end_line_text = get_line_text(doc, my_group.end_line);
41121
+ const my_synthetic_range = {
41122
+ context: my_group.members[0].context,
41123
+ range: {
41124
+ start: { line: my_group.start_line, character: 0 },
41125
+ end: { line: my_group.end_line, character: Number.MAX_SAFE_INTEGER }
41126
+ },
41127
+ start_delimiter: my_group.members[0].start_delimiter,
41128
+ is_single_line: true
41129
+ };
41130
+ the_units.push({
41131
+ content: my_lines.join(`
41132
+ `),
41133
+ range: my_synthetic_range,
41134
+ actual_range: {
41135
+ start: { line: my_group.start_line, character: 0 },
41136
+ end: {
41137
+ line: my_group.end_line,
41138
+ character: my_end_line_text.length
41139
+ }
41140
+ }
41141
+ });
41142
+ }
41143
+ the_units.sort((a, b) => b.actual_range.start.line - a.actual_range.start.line);
41144
+ return the_units;
41145
+ }
40394
41146
  extract_block_content(doc, context_range) {
40395
41147
  const the_start_line = context_range.range.start.line;
40396
41148
  const the_end_line = context_range.end_delimiter ? context_range.end_delimiter.range.start.line : context_range.range.end.line;
@@ -71745,6 +72497,13 @@ async function create_server(options) {
71745
72497
  function configure_completion_provider(settings) {
71746
72498
  completion_provider?.configure_completion(settings.completion);
71747
72499
  }
72500
+ function apply_cache_capacities(settings) {
72501
+ scope_resolver?.set_cache_capacities({
72502
+ max_cached_files: settings.cross_file?.max_cached_files ?? DEFAULT_SETTINGS.cross_file.max_cached_files,
72503
+ max_cached_scopes: settings.cross_file?.max_cached_scopes ?? DEFAULT_SETTINGS.cross_file.max_cached_scopes
72504
+ });
72505
+ forward_scope_resolver?.set_forward_closure_memo_capacity(settings.cross_file?.max_cached_forward_closures ?? DEFAULT_SETTINGS.cross_file.max_cached_forward_closures ?? 2000);
72506
+ }
71748
72507
  function reset_workspace_indexing_state() {
71749
72508
  workspace_indexer?.reset();
71750
72509
  dependency_graph?.reset();
@@ -71757,6 +72516,7 @@ async function create_server(options) {
71757
72516
  }
71758
72517
  last_applied_indexing_signature = indexing_affecting_signature(settings);
71759
72518
  configure_completion_provider(settings);
72519
+ apply_cache_capacities(settings);
71760
72520
  if (workspace_indexer) {
71761
72521
  workspace_indexer.configure(settings);
71762
72522
  workspace_indexer.set_max_indexed_files(settings.cross_file?.max_indexed_files ?? 1000);
@@ -71810,6 +72570,7 @@ async function create_server(options) {
71810
72570
  configure_workspace_indexing(settings, active_workspace_roots, true);
71811
72571
  } else {
71812
72572
  configure_completion_provider(settings);
72573
+ apply_cache_capacities(settings);
71813
72574
  workspace_indexer?.configure(settings);
71814
72575
  revalidate_all_open_docs();
71815
72576
  }
@@ -72125,6 +72886,7 @@ ${scope_resolver.get_reverse_deps_debug_info()}`);
72125
72886
  configure_workspace_indexing(settings, active_workspace_roots, true);
72126
72887
  } else {
72127
72888
  configure_completion_provider(settings);
72889
+ apply_cache_capacities(settings);
72128
72890
  workspace_indexer?.configure(settings);
72129
72891
  revalidate_all_open_docs();
72130
72892
  }
@@ -72472,4 +73234,4 @@ export {
72472
73234
  is_cli_entry_point
72473
73235
  };
72474
73236
 
72475
- //# debugId=5FE144BA73C2961164756E2164756E21
73237
+ //# debugId=DFA4590C23722F1564756E2164756E21