@quolu/lattice 0.38.0 → 0.39.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.
@@ -809,9 +809,12 @@ class TreeSitterExtractor {
809
809
  const left = (0, tree_sitter_helpers_1.getChildByField)(n, 'left') ?? (0, tree_sitter_helpers_1.getChildByField)(n, 'pattern') ?? n.namedChild(0);
810
810
  if (left?.type === 'identifier')
811
811
  bump(left);
812
- else if (left)
812
+ // Attribute/subscript assignment mutates an existing binding's
813
+ // value; only destructuring patterns introduce/rebind names.
814
+ else if (left && ['expression_list', 'pattern_list', 'tuple', 'list', 'list_pattern'].includes(left.type)) {
813
815
  for (const c of left.namedChildren)
814
816
  bump(c);
817
+ }
815
818
  break;
816
819
  }
817
820
  case 'init_declarator': // C `T X = …` (file-scope const AND the local that shadows it)
@@ -863,30 +866,79 @@ class TreeSitterExtractor {
863
866
  if (targets.size === 0)
864
867
  return;
865
868
  }
866
- // Write detection is wired for the TS/JS family only (assignment/augmented/
867
- // update, including member mutation like `cache.x = 1`). Other value-ref
868
- // languages keep read-only edges until their grammars get the same wiring —
869
- // consumers must not read an absent flag as "read-only" for those languages.
870
- const writeAware = ['typescript', 'javascript', 'tsx', 'jsx', 'arkts'].includes(this.language);
869
+ // Write detection is wired for every language that also has a native-kernel
870
+ // value-ref mirror. Languages outside this set keep an absent flag — callers
871
+ // must not interpret absence as read-only.
872
+ const writeAware = [
873
+ 'typescript', 'javascript', 'tsx', 'jsx', 'arkts', 'go', 'python', 'java',
874
+ ].includes(this.language);
871
875
  const isWriteReference = (n) => {
872
- // Climb the leftmost object path: `cache.x.y = 1` / `cache[k] = 1` mutate
876
+ // Climb the binding-root path: `cache.x.y = 1` / `cache[k] = 1` mutate
873
877
  // the binding's value even though the identifier itself is not re-bound.
874
878
  let node = n;
875
879
  let parent = node.parent;
876
- while (parent && (parent.type === 'member_expression' || parent.type === 'subscript_expression')) {
877
- const object = (0, tree_sitter_helpers_1.getChildByField)(parent, 'object');
878
- if (!object || object.startIndex !== node.startIndex || object.endIndex !== node.endIndex)
879
- break;
880
- node = parent;
881
- parent = node.parent;
880
+ while (parent) {
881
+ const rootField = (() => {
882
+ if (parent.type === 'member_expression' || parent.type === 'subscript_expression')
883
+ return 'object';
884
+ if (this.language === 'go'
885
+ && ['selector_expression', 'index_expression', 'slice_expression'].includes(parent.type))
886
+ return 'operand';
887
+ if (this.language === 'python' && parent.type === 'attribute')
888
+ return 'object';
889
+ if (this.language === 'python' && parent.type === 'subscript')
890
+ return 'value';
891
+ if (this.language === 'java' && parent.type === 'field_access')
892
+ return 'object';
893
+ if (this.language === 'java' && parent.type === 'array_access')
894
+ return 'array';
895
+ return null;
896
+ })();
897
+ if (rootField !== null) {
898
+ const root = (0, tree_sitter_helpers_1.getChildByField)(parent, rootField);
899
+ if (!root || root.startIndex !== node.startIndex || root.endIndex !== node.endIndex)
900
+ break;
901
+ node = parent;
902
+ parent = node.parent;
903
+ continue;
904
+ }
905
+ // Go's assignment LHS is wrapped in an expression_list.
906
+ if (this.language === 'go' && parent.type === 'expression_list') {
907
+ node = parent;
908
+ parent = node.parent;
909
+ continue;
910
+ }
911
+ break;
882
912
  }
883
913
  if (!parent)
884
914
  return false;
885
- if (parent.type === 'assignment_expression' || parent.type === 'augmented_assignment_expression') {
886
- const left = (0, tree_sitter_helpers_1.getChildByField)(parent, 'left');
915
+ const isLeft = (owner, fields) => fields.some((field) => {
916
+ const left = (0, tree_sitter_helpers_1.getChildByField)(owner, field);
887
917
  return left !== null && left.startIndex === node.startIndex && left.endIndex === node.endIndex;
918
+ });
919
+ if (['typescript', 'javascript', 'tsx', 'jsx', 'arkts'].includes(this.language)) {
920
+ if (parent.type === 'assignment_expression' || parent.type === 'augmented_assignment_expression') {
921
+ return isLeft(parent, ['left']);
922
+ }
923
+ return parent.type === 'update_expression';
888
924
  }
889
- return parent.type === 'update_expression';
925
+ if (this.language === 'go') {
926
+ if (parent.type === 'assignment_statement')
927
+ return isLeft(parent, ['left']);
928
+ return parent.type === 'inc_statement' || parent.type === 'dec_statement';
929
+ }
930
+ if (this.language === 'python') {
931
+ if (parent.type === 'assignment' || parent.type === 'augmented_assignment') {
932
+ return isLeft(parent, ['left']);
933
+ }
934
+ return parent.type === 'named_expression' && isLeft(parent, ['name', 'left']);
935
+ }
936
+ if (this.language === 'java') {
937
+ if (parent.type === 'assignment_expression')
938
+ return isLeft(parent, ['left']);
939
+ return parent.type === 'update_expression';
940
+ }
941
+ return false;
890
942
  };
891
943
  for (const scope of scopes) {
892
944
  const seen = new Map();