Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Commit af6eb5b

Browse files
committed
Turn quoted access to declared properties into dotted access.
TypeScript allows accessing properties with an element access expression, as long as the argument is a string literal: const x = { y: number; } x['y']; // legal This change turns all those accesses into dotted accesses, to make sure they are consistently renamed by Closure Compiler. const x = { y: number; } x.y; // changed to use dotted access
1 parent 24c1434 commit af6eb5b

File tree

7 files changed

+53
-25
lines changed

7 files changed

+53
-25
lines changed

src/tsickle.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,13 +762,39 @@ class Annotator extends ClosureRewriter {
762762
return true;
763763
}
764764
break;
765+
case ts.SyntaxKind.ElementAccessExpression:
766+
// Convert quoted accesses to properties that have a symbol to dotted accesses, to get
767+
// consistent Closure renaming. This can happen because TS allows quoted access with literal
768+
// strings to properties.
769+
const eae = node as ts.ElementAccessExpression;
770+
if (!eae.argumentExpression ||
771+
eae.argumentExpression.kind !== ts.SyntaxKind.StringLiteral) {
772+
return false;
773+
}
774+
const quotedPropSym = this.typeChecker.getSymbolAtLocation(eae.argumentExpression);
775+
// If it has a symbol, it's actually a regular declared property.
776+
if (!quotedPropSym) return false;
777+
const propName = (eae.argumentExpression as ts.StringLiteral).text;
778+
this.writeNode(eae.expression);
779+
this.emit(`.${propName}`);
780+
return true;
765781
case ts.SyntaxKind.PropertyAccessExpression:
766782
// Convert dotted accesses to types that have an index type declared to quoted accesses, to
767783
// avoid Closure renaming one access but not the other.
768784
// This can happen because TS allows dotted access to string index types.
769785
const pae = node as ts.PropertyAccessExpression;
770786
const t = this.typeChecker.getTypeAtLocation(pae.expression);
771787
if (!t.getStringIndexType()) return false;
788+
// Types can have string index signatures and declared properties (of the matching type).
789+
// These properties have a symbol, as opposed to pure string index types.
790+
const propSym = this.typeChecker.getSymbolAtLocation(pae.name);
791+
// The decision to return below is a judgement call. Presumably, in most situations, dotted
792+
// access to a property is correct, and should not be turned into quoted access even if
793+
// there is a string index on the type. However it is possible to construct programs where
794+
// this is incorrect, e.g. where user code assigns into a property through the index access
795+
// in another location.
796+
if (propSym) return false;
797+
772798
this.debugWarn(
773799
pae,
774800
this.typeChecker.typeToString(t) +

test_files/enum/enum.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ EnumTest1[EnumTest1.PI] = "PI";
1616
// number. Verify that the resulting TypeScript still allows you to
1717
// index into the enum with all the various ways allowed of enums.
1818
let /** @type {number} */ enumTestValue = EnumTest1.XYZ;
19-
let /** @type {number} */ enumTestValue2 = EnumTest1['XYZ'];
19+
let /** @type {number} */ enumTestValue2 = EnumTest1.XYZ;
2020
let /** @type {string} */ enumNumIndex = EnumTest1[((null))];
2121
let /** @type {number} */ enumStrIndex = EnumTest1[((null))];
2222
/**
@@ -25,7 +25,8 @@ let /** @type {number} */ enumStrIndex = EnumTest1[((null))];
2525
*/
2626
function enumTestFunction(val) { }
2727
enumTestFunction(enumTestValue);
28-
let /** @type {number} */ enumTestLookup = EnumTest1["XYZ"];
28+
let /** @type {number} */ enumTestLookup = EnumTest1.XYZ;
29+
let /** @type {?} */ enumTestLookup2 = EnumTest1["xyz".toUpperCase()];
2930
exports.EnumTest2 = {};
3031
/** @type {number} */
3132
exports.EnumTest2.XYZ = 0;

test_files/enum/enum.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ function enumTestFunction(val: EnumTest1) {}
1414
enumTestFunction(enumTestValue);
1515

1616
let enumTestLookup = EnumTest1["XYZ"];
17+
let enumTestLookup2 = EnumTest1["xyz".toUpperCase()];
1718

1819
// This additional exported enum is here to exercise the fix for issue #51.
1920
export enum EnumTest2 {XYZ, PI = 3.14159}

test_files/enum/enum.tsickle.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ EnumTest1[EnumTest1.PI] = "PI";
2121
// number. Verify that the resulting TypeScript still allows you to
2222
// index into the enum with all the various ways allowed of enums.
2323
let /** @type {number} */ enumTestValue: EnumTest1 = EnumTest1.XYZ;
24-
let /** @type {number} */ enumTestValue2: EnumTest1 = EnumTest1['XYZ'];
24+
let /** @type {number} */ enumTestValue2: EnumTest1 = EnumTest1.XYZ;
2525
let /** @type {string} */ enumNumIndex: string = EnumTest1[ /** @type {number} */(( /** @type {?} */((null as any)) as number))];
2626
let /** @type {number} */ enumStrIndex: number = EnumTest1[ /** @type {string} */(( /** @type {?} */((null as any)) as string))];
2727
/**
@@ -31,7 +31,8 @@ let /** @type {number} */ enumStrIndex: number = EnumTest1[ /** @type {string} *
3131
function enumTestFunction(val: EnumTest1) {}
3232
enumTestFunction(enumTestValue);
3333

34-
let /** @type {number} */ enumTestLookup = EnumTest1["XYZ"];
34+
let /** @type {number} */ enumTestLookup = EnumTest1.XYZ;
35+
let /** @type {?} */ enumTestLookup2 = EnumTest1["xyz".toUpperCase()];
3536
export type EnumTest2 = number;
3637
export let EnumTest2: any = {};
3738
/** @type {number} */

test_files/quote_props/quote.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
goog.module('test_files.quote_props.quote');var module = module || {id: 'test_files/quote_props/quote.js'};/**
1+
goog.module('test_files.quote_props.quote');var module = module || {id: 'test_files/quote_props/quote.js'};
2+
/**
23
* @record
34
*/
45
function Quoted() { }
@@ -13,10 +14,8 @@ quoted['hello'] = 1;
1314
function QuotedMixed() { }
1415
/** @type {number} */
1516
QuotedMixed.prototype.foo;
16-
// TODO(martinprobst): should 'foo: 1' below be quoted?
1717
let /** @type {!QuotedMixed} */ quotedMixed = { foo: 1 };
18-
console.log(quotedMixed['foo']);
19-
// TODO(martinprobst): should this access to a declared property be quoted?
20-
quotedMixed['foo'] = 1;
21-
// TODO(martinprobst): should this access to a declared property be un-quoted?
22-
quotedMixed['foo'] = 1;
18+
console.log(quotedMixed.foo);
19+
quotedMixed.foo = 1;
20+
// Should be converted to non-quoted access.
21+
quotedMixed.foo = 1;

test_files/quote_props/quote.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// silence warnings about redeclaring vars.
2+
export {};
3+
14
interface Quoted {
25
[k: string]: number;
36
}
@@ -13,11 +16,9 @@ interface QuotedMixed extends Quoted {
1316
// access this field in a mixed fashion.
1417
foo: number;
1518
}
16-
// TODO(martinprobst): should 'foo: 1' below be quoted?
1719
let quotedMixed: QuotedMixed = {foo: 1};
1820
console.log(quotedMixed.foo);
1921

20-
// TODO(martinprobst): should this access to a declared property be quoted?
2122
quotedMixed.foo = 1;
22-
// TODO(martinprobst): should this access to a declared property be un-quoted?
23+
// Should be converted to non-quoted access.
2324
quotedMixed['foo'] = 1;

test_files/quote_props/quote.tsickle.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
Warning at test_files/quote_props/quote.ts:6:13: Quoted has a string index type but is accessed using dotted access. Quoting the access.
2-
Warning at test_files/quote_props/quote.ts:7:1: Quoted has a string index type but is accessed using dotted access. Quoting the access.
3-
Warning at test_files/quote_props/quote.ts:18:13: QuotedMixed has a string index type but is accessed using dotted access. Quoting the access.
4-
Warning at test_files/quote_props/quote.ts:21:1: QuotedMixed has a string index type but is accessed using dotted access. Quoting the access.
1+
Warning at test_files/quote_props/quote.ts:9:13: Quoted has a string index type but is accessed using dotted access. Quoting the access.
2+
Warning at test_files/quote_props/quote.ts:10:1: Quoted has a string index type but is accessed using dotted access. Quoting the access.
53
====
6-
4+
// silence warnings about redeclaring vars.
5+
export {};
76
/**
87
* @record
98
*/
109
function Quoted() {}
1110
/* TODO: handle strange member:
1211
[k: string]: number;
1312
*/
13+
14+
1415
interface Quoted {
1516
[k: string]: number;
1617
}
@@ -34,11 +35,9 @@ interface QuotedMixed extends Quoted {
3435
// access this field in a mixed fashion.
3536
foo: number;
3637
}
37-
// TODO(martinprobst): should 'foo: 1' below be quoted?
3838
let /** @type {!QuotedMixed} */ quotedMixed: QuotedMixed = {foo: 1};
39-
console.log(quotedMixed['foo']);
39+
console.log(quotedMixed.foo);
4040

41-
// TODO(martinprobst): should this access to a declared property be quoted?
42-
quotedMixed['foo'] = 1;
43-
// TODO(martinprobst): should this access to a declared property be un-quoted?
44-
quotedMixed['foo'] = 1;
41+
quotedMixed.foo = 1;
42+
// Should be converted to non-quoted access.
43+
quotedMixed.foo = 1;

0 commit comments

Comments
 (0)