microsoft / microsoft/TypeScript

Proposal: quoted and unquoted property names distinct

オープン
#14,267 コメント 5 件 リアクション 11 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

In Discussion Suggestion
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
2日 4時間
マージ済み PR(30日)
132

説明

Closure Compiler is a JavaScript optimizer that also works well with TypeScript (using our https://github.com/angular/tsickle as an intermediate re-writer). It produces the smallest bundles, and we use this internally at Google and externally for some Angular users to get the smallest application.

In ADVANCED_OPTIMIZATIONS mode, Closure Compiler renames non-local properties. It uses a simple rule: unquoted property accesses can be renamed, but quoted property access cannot. This can break a program in the presence of mixed quoted and non-quoted property access, as a trivial example:

window.foo = "hello world";
console.log(window["foo"]);

Is minified by Closure Compiler [1] as

window.a = "hello world";
console.log(window.foo); // prints "undefined"

Currently, Closure Compiler puts the burden of correct quoted/unquoted access on the author. This is documented here: https://developers.google.com/closure/compiler/docs/api-tutorial3#propnames

With TypeScript's type-checker we believe we could flag the majority of cases where property renaming breaks a users program. We propose to introduce an option that makes quoted and unquoted properties be separate, non-matching members. In the proposal below, assume we enable this behavior with an option --strictPropertyNaming

Treat quoted and unquoted properties as distinct

Currently, TypeScript allows quoted access to named properties (and vice versa):

interface SeemsSafe {
    foo?: {};
}

let b: SeemsSafe = {};
b["foo"] = 1; // This access should fail under --strictPropertyNaming
b.foo = 2; // okay

Also, newly introduced in TypeScript 2.2, the inverse problem exists:

interface HasIndexSig {
  [key: string]: boolean;
}
let c: HasIndexSig;
c.foo = true; // This access should fail under --strictPropertyNaming

Defining types whose members should not be renamed

It's convenient for users to specify a type that insures the properties are not renamed. For example, when an XHR returns, property accesses of the JSON data must not be renamed.

// Under --strictPropertyNaming, the quotes on these properties matter.
// They must be accessed quoted, not unquoted.
interface JSONData {
  'username': string;
  'phone': number;
}

let data = JSON.parse(xhrResult.text) as JSONData;
console.log(data['phone']); // okay
console.log(data.username); // should be error under --strictPropertyNaming 

Structural matches

Two types should not be a structural match if their quoting differs:

interface JSONData {
  'username': string;
}

class SomeInternalType {
  username: string;
}

let data = JSON.parse(xhrResult.text) as JSONData;
let myObj: SomeInternalType = data;  // should fail under --strictPropertyNaming
console.log(myObj.username); // would get broken by property renaming

Avoid a mix of Index Signatures and named properties

Optional: we could add a semantic check for .ts inputs that disallows any type to have both an index signature and named properties.

interface Unsafe {
    [prop: string]: {};
    foo?: {}; // This could be a semantic error with --strictPropertyNaming
}

let a: Unsafe = {};
a.foo = 1;
a["foo"] = 2;

Note that the intersection operator & still defeats such a check:

type Unsafe = {
    [prop: string]: {};
} & {
    foo?: {}; // This is uncheckable because the intersection never fails
}

We should not check .d.ts inputs as they may have been compiled without --strictPropertyNaming.

Compatibility with libraries

If a library is developed with --strictPropertyNaming, the resulting .d.ts files should be usable by any program whether it opts into the flag or not. There is one corner case however.

The following example should probably produce a declarationDiagnostic, because the generated .d.ts would be an error when used in a compilation without --strictPropertyNaming.

type C = {
  a: number;
  'a': string; // this one should probably error
  [key: string]: number;
}

A simpler alternative is just to allow this case, and produce it in .d.ts files. Then downstream consumers will have an error unless they turn on --strictPropertyNaming or --noLibCheck.

Either choice here is okay with us.

Property names that require quoting

In this case, there is no choice but to quote the identifier:

interface JSONData {
  'hy-phen': number;
}

This continues to work under --strictPropertyNaming, but the implication is that such an identifier is forced to be non-renamable since there is no unquoted syntax to declare it. This seems fine, it results in a lost optimization only for such names, which we assume are rare.

Property-renaming safety

There are some cases that will remain unsafe:

  • the any type still turns off type-checking, including checking quoted vs. unquoted access
  • libraries developed without --strictPropertyNaming use unquoted identifiers which should not be renamed (such as document.getElementById. Closure Compiler already has an 'externs' mechanism that prevents the renaming. In the TypeScript code it will not be evident that the properties are not renamed, but this is the same situation we have today.

[1] https://closure-compiler.appspot.com/home#code%3D%252F%252F%2520%253D%253DClosureCompiler%253D%253D%250A%252F%252F%2520%2540compilation_level%2520ADVANCED_OPTIMIZATIONS%250A%252F%252F%2520%2540output_file_name%2520default.js%250A%252F%252F%2520%2540formatting%2520pretty_print%250A%252F%252F%2520%253D%253D%252FClosureCompiler%253D%253D%250A%250Awindow.foo%2520%253D%2520%2522hello%2520world%2522%253B%250Aconsole.log(window%255B%2522foo%2522%255D)%253B%250A

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず、提案で説明されている、引用符付きおよび引用符なしのプロパティアクセス、インデックスシグネチャ、構造的互換性に関する TypeScript の型チェッカーの動作を確認します。--strictPropertyNaming のセマンティクスを定義し、例のテストを実装する前に、宣言ファイルとインデックスシグネチャの互換性に関する選択を解決します。完了とは、提案された診断と互換性の動作がカバーされていることを意味します。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
javascript, typescript
領域
compilers
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
25/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。