fix(compiler): emit quoted object literal keys if the source is quoted

feat(tsc-wrapped): recored when to quote a object literal key

Collecting quoted literals is off by default as it introduces
a breaking change in the .metadata.json file. A follow-up commit
will address this.

Fixes #13249
Closes #13356
This commit is contained in:
Chuck Jazdzewski
2016-12-12 10:49:17 -08:00
committed by Victor Berchet
parent f238c8ac7a
commit dd0519abad
10 changed files with 136 additions and 21 deletions

View File

@ -13,11 +13,22 @@ import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, Me
import {Symbols} from './symbols';
/**
* A set of collector options to use when collecting metadata.
*/
export class CollectorOptions {
/**
* Collect a hidden field "$quoted$" in objects literals that record when the key was quoted in
* the source.
*/
quotedNames?: boolean;
}
/**
* Collect decorator metadata from a TypeScript module.
*/
export class MetadataCollector {
constructor() {}
constructor(private options: CollectorOptions = {}) {}
/**
* Returns a JSON.stringify friendly form describing the decorators of the exported classes from
@ -26,7 +37,7 @@ export class MetadataCollector {
public getMetadata(sourceFile: ts.SourceFile, strict: boolean = false): ModuleMetadata {
const locals = new Symbols(sourceFile);
const nodeMap = new Map<MetadataValue|ClassMetadata|FunctionMetadata, ts.Node>();
const evaluator = new Evaluator(locals, nodeMap);
const evaluator = new Evaluator(locals, nodeMap, this.options);
let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
let exports: ModuleExportMetadata[];

View File

@ -8,6 +8,7 @@
import * as ts from 'typescript';
import {CollectorOptions} from './collector';
import {MetadataEntry, MetadataError, MetadataGlobalReferenceExpression, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {Symbols} from './symbols';
@ -97,7 +98,9 @@ export function errorSymbol(
* possible.
*/
export class Evaluator {
constructor(private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>) {}
constructor(
private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>,
private options: CollectorOptions = {}) {}
nameOf(node: ts.Node): string|MetadataError {
if (node.kind == ts.SyntaxKind.Identifier) {
@ -223,11 +226,16 @@ export class Evaluator {
switch (node.kind) {
case ts.SyntaxKind.ObjectLiteralExpression:
let obj: {[name: string]: any} = {};
let quoted: string[] = [];
ts.forEachChild(node, child => {
switch (child.kind) {
case ts.SyntaxKind.ShorthandPropertyAssignment:
case ts.SyntaxKind.PropertyAssignment:
const assignment = <ts.PropertyAssignment|ts.ShorthandPropertyAssignment>child;
if (assignment.name.kind == ts.SyntaxKind.StringLiteral) {
const name = (assignment.name as ts.StringLiteral).text;
quoted.push(name);
}
const propertyName = this.nameOf(assignment.name);
if (isMetadataError(propertyName)) {
error = propertyName;
@ -245,6 +253,9 @@ export class Evaluator {
}
});
if (error) return error;
if (this.options.quotedNames && quoted.length) {
obj['$quoted$'] = quoted;
}
return obj;
case ts.SyntaxKind.ArrayLiteralExpression:
let arr: MetadataValue[] = [];

View File

@ -50,7 +50,7 @@ describe('Collector', () => {
]);
service = ts.createLanguageService(host, documentRegistry);
program = service.getProgram();
collector = new MetadataCollector();
collector = new MetadataCollector({quotedNames: true});
});
it('should not have errors in test data', () => { expectValidSources(service, program); });
@ -164,11 +164,16 @@ describe('Collector', () => {
version: 2,
metadata: {
HEROES: [
{'id': 11, 'name': 'Mr. Nice'}, {'id': 12, 'name': 'Narco'},
{'id': 13, 'name': 'Bombasto'}, {'id': 14, 'name': 'Celeritas'},
{'id': 15, 'name': 'Magneta'}, {'id': 16, 'name': 'RubberMan'},
{'id': 17, 'name': 'Dynama'}, {'id': 18, 'name': 'Dr IQ'}, {'id': 19, 'name': 'Magma'},
{'id': 20, 'name': 'Tornado'}
{'id': 11, 'name': 'Mr. Nice', '$quoted$': ['id', 'name']},
{'id': 12, 'name': 'Narco', '$quoted$': ['id', 'name']},
{'id': 13, 'name': 'Bombasto', '$quoted$': ['id', 'name']},
{'id': 14, 'name': 'Celeritas', '$quoted$': ['id', 'name']},
{'id': 15, 'name': 'Magneta', '$quoted$': ['id', 'name']},
{'id': 16, 'name': 'RubberMan', '$quoted$': ['id', 'name']},
{'id': 17, 'name': 'Dynama', '$quoted$': ['id', 'name']},
{'id': 18, 'name': 'Dr IQ', '$quoted$': ['id', 'name']},
{'id': 19, 'name': 'Magma', '$quoted$': ['id', 'name']},
{'id': 20, 'name': 'Tornado', '$quoted$': ['id', 'name']}
]
}
});