diff --git a/common/changes/@neo-one/smart-contract-compiler/new-ts_2020-02-27-21-52.json b/common/changes/@neo-one/smart-contract-compiler/new-ts_2020-02-27-21-52.json new file mode 100644 index 0000000000..7c8fa80516 --- /dev/null +++ b/common/changes/@neo-one/smart-contract-compiler/new-ts_2020-02-27-21-52.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@neo-one/smart-contract-compiler", + "comment": "Add support for new language features in TS v3.7, v3.8", + "type": "minor" + } + ], + "packageName": "@neo-one/smart-contract-compiler", + "email": "spencercorwin@icloud.com" +} \ No newline at end of file diff --git a/common/changes/@neo-one/smart-contract-compiler/upgrade-ts_2020-02-26-01-16.json b/common/changes/@neo-one/smart-contract-compiler/upgrade-ts_2020-02-26-01-16.json index a83281c3db..69b1a7abee 100644 --- a/common/changes/@neo-one/smart-contract-compiler/upgrade-ts_2020-02-26-01-16.json +++ b/common/changes/@neo-one/smart-contract-compiler/upgrade-ts_2020-02-26-01-16.json @@ -2,10 +2,10 @@ "changes": [ { "packageName": "@neo-one/smart-contract-compiler", - "comment": "Upgrade TS to v3.8.1-rc. Add support for Nullish Coalescing", + "comment": "Upgrade TS to v3.8.1-rc. Add support for Nullish Coalescing.", "type": "minor" } ], "packageName": "@neo-one/smart-contract-compiler", "email": "spencercorwin@icloud.com" -} \ No newline at end of file +} diff --git a/common/changes/@neo-one/typescript-concatenator/new-ts_2020-02-27-21-52.json b/common/changes/@neo-one/typescript-concatenator/new-ts_2020-02-27-21-52.json new file mode 100644 index 0000000000..9ca3484388 --- /dev/null +++ b/common/changes/@neo-one/typescript-concatenator/new-ts_2020-02-27-21-52.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@neo-one/typescript-concatenator", + "comment": "Add support for new language features in TS v3.7, v3.8", + "type": "minor" + } + ], + "packageName": "@neo-one/typescript-concatenator", + "email": "spencercorwin@icloud.com" +} \ No newline at end of file diff --git a/common/changes/@neo-one/typescript-concatenator/upgrade-ts_2020-02-26-01-16.json b/common/changes/@neo-one/typescript-concatenator/upgrade-ts_2020-02-26-01-16.json index fa103f47d0..e26e7190a2 100644 --- a/common/changes/@neo-one/typescript-concatenator/upgrade-ts_2020-02-26-01-16.json +++ b/common/changes/@neo-one/typescript-concatenator/upgrade-ts_2020-02-26-01-16.json @@ -2,10 +2,10 @@ "changes": [ { "packageName": "@neo-one/typescript-concatenator", - "comment": "Upgrade TS to v3.8.1-rc. Add support for namespace exports", + "comment": "Upgrade TS to v3.8.1-rc. Add support for namespace exports.", "type": "minor" } ], "packageName": "@neo-one/typescript-concatenator", "email": "spencercorwin@icloud.com" -} \ No newline at end of file +} diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/ClassDeclarationCompiler.test.ts b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/ClassDeclarationCompiler.test.ts index 21fc46dde5..f081393406 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/ClassDeclarationCompiler.test.ts +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/ClassDeclarationCompiler.test.ts @@ -105,6 +105,74 @@ describe('ClassDeclarationCompiler', () => { `); }); + test('basic class with ECMAScript private member, inaccessible', async () => { + helpers.compileString( + ` + class Foo { + #x: string = 'bar'; + + ['bar'](): string { + return this.#x; + } + } + + const f = new Foo(); + f.#x; + `, + { type: 'error' }, + ); + }); + + test('ECMAScript private member, no public modifier allowed', async () => { + helpers.compileString( + ` + class Foo { + public #x: string = 'bar'; + } + `, + { type: 'error' }, + ); + }); + + test('ECMAScript private member, no private modifier allowed', async () => { + helpers.compileString( + ` + class Foo { + private #x: string = 'bar'; + } + `, + { type: 'error' }, + ); + }); + + test('ECMAScript private member, extends does not override private member', async () => { + await helpers.executeString( + ` + class Foo { + #x: string = 'bar'; + + getX(): string { + return this.#x; + } + } + + class Bar extends Foo { + #x: string = 'baz'; + + getX(): string { + return this.#x; + } + } + + const foo = new Foo(); + const bar = new Bar(); + + assertEqual(foo.getX(), 'bar'); + assertEqual(bar.getX(), 'baz'); + `, + ); + }); + test('basic class with get accessor', async () => { await helpers.executeString(` class Foo { diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/TypeAliasDeclarationCompiler.test.ts b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/TypeAliasDeclarationCompiler.test.ts index 362a1d4dae..45f86ad5eb 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/TypeAliasDeclarationCompiler.test.ts +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/TypeAliasDeclarationCompiler.test.ts @@ -14,4 +14,26 @@ describe('TypeAliasDeclarationCompiler', () => { } `); }); + + test('recursive type alias does not emit', async () => { + await helpers.executeString(` + type Json = + | string + | number + | boolean + | null + | { [property: string]: Json } + | Json[]; + + type VirtualNode = + | string + | [string, { [key: string]: any }, ...VirtualNode[]]; + + const myNode: VirtualNode = + ["div", { id: "parent" }, + ["div", { id: "first-child" }, "I'm the first child"], + ["div", { id: "second-child" }, "I'm the second child"] + ]; + `); + }); }); diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/__snapshots__/ClassDeclarationCompiler.test.ts.snap b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/__snapshots__/ClassDeclarationCompiler.test.ts.snap index 2c2100c1ce..74a4b2103d 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/__snapshots__/ClassDeclarationCompiler.test.ts.snap +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/declaration/__snapshots__/ClassDeclarationCompiler.test.ts.snap @@ -1,5 +1,40 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`ClassDeclarationCompiler ECMAScript private member, no private modifier allowed 1`] = ` +"snippetCode.ts (3,9): An accessibility modifier cannot be used with a private identifier. + + 1 | + 2 | class Foo { + > 3 | private #x: string = 'bar'; + | ^ + 4 | } + 5 | +" +`; + +exports[`ClassDeclarationCompiler ECMAScript private member, no public modifier allowed 1`] = ` +"snippetCode.ts (3,9): An accessibility modifier cannot be used with a private identifier. + + 1 | + 2 | class Foo { + > 3 | public #x: string = 'bar'; + | ^ + 4 | } + 5 | +" +`; + +exports[`ClassDeclarationCompiler basic class with ECMAScript private member, inaccessible 1`] = ` +"snippetCode.ts (11,9): Property '#x' is not accessible outside class 'Foo' because it has a private identifier. + + 9 | + 10 | const f = new Foo(); + > 11 | f.#x; + | ^ + 12 | +" +`; + exports[`ClassDeclarationCompiler decorators 1`] = ` "snippetCode.ts (7,9): Custom decorators are not supported diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/AwaitExpressionCompiler.test.ts b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/AwaitExpressionCompiler.test.ts index 396334eec4..10f1ae4dcf 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/AwaitExpressionCompiler.test.ts +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/AwaitExpressionCompiler.test.ts @@ -9,4 +9,15 @@ describe('AwaitFunctionCompiler', () => { { type: 'error' }, ); }); + + test('await', async () => { + helpers.compileString( + ` + await 2; + + export {}; + `, + { type: 'error' }, + ); + }); }); diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/CallExpressionCompiler.test.ts b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/CallExpressionCompiler.test.ts index 1774406bf8..037dacb501 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/CallExpressionCompiler.test.ts +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/CallExpressionCompiler.test.ts @@ -79,6 +79,21 @@ describe('CallExpressionCompiler', () => { `); }); + test('nested property call', async () => { + await helpers.executeString(` + const foo = { + x: 1, + y: { + z(): number { + return 13; + } + } + }; + + assertEqual(foo.y.z(), 13); + `); + }); + test('method call', async () => { await helpers.executeString(` class Foo { @@ -264,4 +279,238 @@ describe('CallExpressionCompiler', () => { assertEqual(x[Symbol.iterator]() !== undefined, true); `); }); + + test('optional chain - element access with symbol - undefined', async () => { + await helpers.executeString(` + const foo = Symbol.for('hello'); + const bar: { [foo]: (() => number) | null | undefined } = { [foo]: undefined } as unknown as { [foo]: (() => number) | null | undefined }; + + assertEqual(bar[foo]?.(), undefined); + `); + }); + + test('optional chain - element access with symbol - null', async () => { + await helpers.executeString(` + const foo = Symbol.for('hello'); + const bar: { [foo]: (() => number) | null | undefined } = { [foo]: null } as unknown as { [foo]: (() => number) | null | undefined }; + + assertEqual(bar[foo]?.(), undefined); + `); + }); + + test('optional chain - element access with symbol - defined', async () => { + await helpers.executeString(` + const foo = Symbol.for('hello'); + const bar: { [foo]: (() => number) | null | undefined } = { [foo]: () => 10 } as unknown as { [foo]: (() => number) | null | undefined }; + + assertEqual(bar[foo]?.(), 10); + `); + }); + + test('optional chain - element access with symbol - nested undefined', async () => { + await helpers.executeString(` + const foo = Symbol.for('hello'); + const baz = Symbol.for('world'); + const bar: { [foo]: { [baz]: (() => number) | null | undefined } } = { [foo]: { [baz]: undefined } } as unknown as { [foo]: { [baz]: (() => number) | null | undefined } }; + + assertEqual(bar[foo][baz]?.(), undefined); + `); + }); + + test('optional chain - element access with symbol - nested null', async () => { + await helpers.executeString(` + const foo = Symbol.for('hello'); + const baz = Symbol.for('world'); + const bar: { [foo]: { [baz]: (() => number) | null | undefined } } = { [foo]: { [baz]: null } } as unknown as { [foo]: { [baz]: (() => number) | null | undefined } }; + + assertEqual(bar[foo][baz]?.(), undefined); + `); + }); + + test('optional chain - element access with symbol - nested defined', async () => { + await helpers.executeString(` + const foo = Symbol.for('hello'); + const baz = Symbol.for('world'); + const bar: { [foo]: { [baz]: (() => number) | null | undefined } } = { [foo]: { [baz]: () => 10 } } as unknown as { [foo]: { [baz]: (() => number) | null | undefined } }; + + assertEqual(bar[foo][baz]?.(), 10); + `); + }); + + test('optional chain - element access with number - undefined', async () => { + await helpers.executeString(` + const foo = 0; + const bar: { [foo]: (() => number) | null | undefined } = { [foo]: undefined } as unknown as { [foo]: (() => number) | null | undefined }; + + assertEqual(bar[foo]?.(), undefined); + `); + }); + + test('optional chain - element access with number - null', async () => { + await helpers.executeString(` + const foo = 0; + const bar: { [foo]: (() => number) | null | undefined } = { [foo]: null } as unknown as { [foo]: (() => number) | null | undefined }; + + assertEqual(bar[foo]?.(), undefined); + `); + }); + + test('optional chain - element access with number - defined', async () => { + await helpers.executeString(` + const foo = 0; + const bar: { [foo]: (() => number) | null | undefined } = { [foo]: () => 10 } as unknown as { [foo]: (() => number) | null | undefined }; + + assertEqual(bar[foo]?.(), 10); + `); + }); + + test('optional chain - element access with number - nested undefined', async () => { + await helpers.executeString(` + const foo = 0; + const baz = 2; + const bar: { [foo]: { [baz]: (() => number) | null | undefined } } = { [foo]: { [baz]: undefined } } as unknown as { [foo]: { [baz]: (() => number) | null | undefined } }; + + assertEqual(bar[foo][baz]?.(), undefined); + `); + }); + + test('optional chain - element access with number - nested null', async () => { + await helpers.executeString(` + const foo = 0; + const baz = 2; + const bar: { [foo]: { [baz]: (() => number) | null | undefined } } = { [foo]: { [baz]: null } } as unknown as { [foo]: { [baz]: (() => number) | null | undefined } }; + + assertEqual(bar[foo][baz]?.(), undefined); + `); + }); + + test('optional chain - element access with number - nested defined', async () => { + await helpers.executeString(` + const foo = 0; + const baz = 2; + const bar: { [foo]: { [baz]: (() => number) | null | undefined } } = { [foo]: { [baz]: () => 10 } } as unknown as { [foo]: { [baz]: (() => number) | null | undefined } }; + + assertEqual(bar[foo][baz]?.(), 10); + `); + }); + + test('optional chain - element access with string - undefined', async () => { + await helpers.executeString(` + const bar: { foo: (() => number) | null | undefined } = { foo: undefined } as unknown as { foo: (() => number) | null | undefined }; + + assertEqual(bar['foo']?.(), undefined); + `); + }); + + test('optional chain - element access with string - null', async () => { + await helpers.executeString(` + const bar: { foo: (() => number) | null | undefined } = { foo: null } as unknown as { foo: (() => number) | null | undefined }; + + assertEqual(bar['foo']?.(), undefined); + `); + }); + + test('optional chain - element access with string - defined', async () => { + await helpers.executeString(` + const bar: { foo: (() => number) | null | undefined } = { foo: () => 10 } as unknown as { foo: (() => number) | null | undefined }; + + assertEqual(bar['foo']?.(), 10); + `); + }); + + test('optional chain - element access with string - nested undefined', async () => { + await helpers.executeString(` + const bar: { foo: { baz: (() => number) | null | undefined } } = { foo: { baz: undefined } } as unknown as { foo: { baz: (() => number) | null | undefined } }; + + assertEqual(bar['foo']['baz']?.(), undefined); + `); + }); + + test('optional chain - element access with string - nested null', async () => { + await helpers.executeString(` + const bar: { foo: { baz: (() => number) | null | undefined } } = { foo: { baz: null } } as unknown as { foo: { baz: (() => number) | null | undefined } }; + + assertEqual(bar['foo']['baz']?.(), undefined); + `); + }); + + test('optional chain - element access with string - nested defined', async () => { + await helpers.executeString(` + const bar: { foo: { baz: (() => number) | null | undefined } } = { foo: { baz: () => 10 } } as unknown as { foo: { baz: (() => number) | null | undefined } }; + + assertEqual(bar['foo']['baz']?.(), 10); + `); + }); + + test('optional chain - call expression - undefined', async () => { + await helpers.executeString(` + const bar: (() => number) | null | undefined = null as unknown as (() => number) | null | undefined; + + assertEqual(bar?.(), undefined); + `); + }); + + test('optional chain - call expression - null', async () => { + await helpers.executeString(` + const bar: (() => number) | null | undefined = undefined as unknown as (() => number) | null | undefined; + + assertEqual(bar?.(), undefined); + `); + }); + + test('optional chain - call expression - defined', async () => { + await helpers.executeString(` + const bar: (() => number) | null | undefined = (() => 10) as unknown as (() => number) | null | undefined; + + assertEqual(bar?.(), 10); + `); + }); + + test('optional chain - property access - nested undefined', async () => { + await helpers.executeString(` + const bar: { foo: (() => number) | null | undefined } = { foo: undefined } as unknown as { foo: (() => number) | null | undefined }; + + assertEqual(bar.foo?.(), undefined); + `); + }); + + test('optional chain - property access - nested null', async () => { + await helpers.executeString(` + const bar: { foo: (() => number) | null | undefined } = { foo: null } as unknown as { foo: (() => number) | null | undefined }; + + assertEqual(bar.foo?.(), undefined); + `); + }); + + test('optional chain - property access - nested defined', async () => { + await helpers.executeString(` + const bar: { foo: (() => number) | null | undefined } = { foo: () => 10 } as unknown as { foo: (() => number) | null | undefined }; + + assertEqual(bar.foo?.(), 10); + `); + }); + + test('optional chain - property access - double nested undefined', async () => { + await helpers.executeString(` + const bar: { foo: { baz: (() => number) | null | undefined } } = { foo: { baz: undefined } } as unknown as { foo: { baz: (() => number) | null | undefined } }; + + assertEqual(bar.foo.baz?.(), undefined); + `); + }); + + test('optional chain - property access - double nested null', async () => { + await helpers.executeString(` + const bar: { foo: { baz: (() => number) | null | undefined } } = { foo: { baz: null } } as unknown as { foo: { baz: (() => number) | null | undefined } }; + + assertEqual(bar.foo.baz?.(), undefined); + `); + }); + + test('optional chain - property access - double nested defined', async () => { + await helpers.executeString(` + const bar: { foo: { baz: (() => number) | null | undefined } } = { foo: { baz: () => 10 } } as unknown as { foo: { baz: (() => number) | null | undefined } }; + + assertEqual(bar.foo.baz?.(), 10); + `); + }); }); diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ElementAccessExpressionCompiler.test.ts b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ElementAccessExpressionCompiler.test.ts index 7545164849..3fe64ac9d4 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ElementAccessExpressionCompiler.test.ts +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ElementAccessExpressionCompiler.test.ts @@ -146,4 +146,28 @@ describe('ElementAccessExpressionCompiler', () => { { type: 'error' }, ); }); + + test('optional element access returns undefined when undefined', async () => { + await helpers.executeString(` + const bar: { optionalProp: number } | null | undefined = null as { optionalProp: number } | null | undefined; + + assertEqual(bar?.['optionalProp'], undefined); + `); + }); + + test('optional element access returns undefined when null', async () => { + await helpers.executeString(` + const bar: { optionalProp: number } | null | undefined = undefined as { optionalProp: number } | null | undefined; + + assertEqual(bar?.['optionalProp'], undefined); + `); + }); + + test('optional element access returns property when defined', async () => { + await helpers.executeString(` + const bar: { optionalProp: number } | null | undefined = { optionalProp: 10 } as { optionalProp: number } | null | undefined; + + assertEqual(bar?.['optionalProp'], 10); + `); + }); }); diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ObjectLiteralExpressionCompiler.test.ts b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ObjectLiteralExpressionCompiler.test.ts index fb694f3a28..0d5a7ec384 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ObjectLiteralExpressionCompiler.test.ts +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/ObjectLiteralExpressionCompiler.test.ts @@ -116,4 +116,19 @@ describe('ObjectLiteralExpressionCompiler', () => { assertEqual(x.f, 4); `); }); + + test('private field identifier fails outside class', async () => { + helpers.compileString( + ` + const y = { + a: 0, + #b: 10, + get f(): number { + return 4; + }, + }; + `, + { type: 'error' }, + ); + }); }); diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/PropertyAccessExpressionCompiler.test.ts b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/PropertyAccessExpressionCompiler.test.ts index fc2ca3c2a8..fc64eca970 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/PropertyAccessExpressionCompiler.test.ts +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/PropertyAccessExpressionCompiler.test.ts @@ -37,4 +37,52 @@ describe('PropertyAccessExpressionCompiler', () => { assertEqual(bar.length, 3); `); }); + + test('optional chaining returns undefined when undefined', async () => { + await helpers.executeString(` + const bar: { optionalProp: number } | null | undefined = null as { optionalProp: number } | null | undefined; + + assertEqual(bar?.optionalProp, undefined); + `); + }); + + test('optional chaining returns undefined when null', async () => { + await helpers.executeString(` + const bar: { optionalProp: number } | null | undefined = undefined as { optionalProp: number } | null | undefined; + + assertEqual(bar?.optionalProp, undefined); + `); + }); + + test('optional chaining returns property when defined', async () => { + await helpers.executeString(` + const bar: { optionalProp: number } | null | undefined = { optionalProp: 10 } as { optionalProp: number } | null | undefined; + + assertEqual(bar?.optionalProp, 10); + `); + }); + + test('nested optional chaining returns undefined when null', async () => { + await helpers.executeString(` + const bar: { first?: { second?: number } | null } | null | undefined = { first: null } as { first?: { second?: number } | null } | null | undefined; + + assertEqual(bar?.first?.second, undefined); + `); + }); + + test('nested optional chaining returns undefined when undefined', async () => { + await helpers.executeString(` + const bar: { first?: { second?: number } | null } | null | undefined = { first: undefined } as { first?: { second: number } | null } | null | undefined; + + assertEqual(bar?.first?.second, undefined); + `); + }); + + test('nested optional chaining returns property when defined', async () => { + await helpers.executeString(` + const bar: { first?: { second?: number } } | null | undefined = { first: { second: 10 } } as { first?: { second: number } } | null | undefined; + + assertEqual(bar?.first?.second, 10); + `); + }); }); diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/__snapshots__/AwaitExpressionCompiler.test.ts.snap b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/__snapshots__/AwaitExpressionCompiler.test.ts.snap index 2070240693..d2a7e78b16 100644 --- a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/__snapshots__/AwaitExpressionCompiler.test.ts.snap +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/__snapshots__/AwaitExpressionCompiler.test.ts.snap @@ -9,3 +9,15 @@ exports[`AwaitFunctionCompiler await 1`] = ` 3 | " `; + +exports[`AwaitFunctionCompiler await 2`] = ` +"snippetCode.ts (2,7): Unsupported syntax. + + 1 | + > 2 | await 2; + | ^ + 3 | + 4 | export {}; + 5 | +" +`; diff --git a/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/__snapshots__/ObjectLiteralExpressionCompiler.test.ts.snap b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/__snapshots__/ObjectLiteralExpressionCompiler.test.ts.snap new file mode 100644 index 0000000000..46bc69060c --- /dev/null +++ b/packages/neo-one-smart-contract-compiler/src/__tests__/compile/expression/__snapshots__/ObjectLiteralExpressionCompiler.test.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ObjectLiteralExpressionCompiler private field identifier fails outside class 1`] = ` +"snippetCode.ts (4,9): Private identifiers are not allowed outside class bodies. + + 2 | const y = { + 3 | a: 0, + > 4 | #b: 10, + | ^ + 5 | get f(): number { + 6 | return 4; + 7 | }, +" +`; diff --git a/packages/neo-one-smart-contract-compiler/src/compile/expression/ElementAccessExpressionCompiler.ts b/packages/neo-one-smart-contract-compiler/src/compile/expression/ElementAccessExpressionCompiler.ts index 21ebd92f05..98ab7fa8fe 100644 --- a/packages/neo-one-smart-contract-compiler/src/compile/expression/ElementAccessExpressionCompiler.ts +++ b/packages/neo-one-smart-contract-compiler/src/compile/expression/ElementAccessExpressionCompiler.ts @@ -12,6 +12,7 @@ export class ElementAccessExpressionCompiler extends NodeCompiler { + // [] + sb.emitOp(expr, 'DROP'); + // [undefinedVal] + sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined); + }; + + const throwTypeErrorUnlessOptionalChain = (innerOptions: VisitOptions) => { + isOptionalChain ? processUndefined(innerOptions) : throwTypeError(innerOptions); + }; + const createHandleProp = ( handleString: (options: VisitOptions) => void, handleNumber: (options: VisitOptions) => void, @@ -469,12 +481,12 @@ export class ElementAccessExpressionCompiler extends NodeCompiler { + // [] + sb.emitOp(expr, 'DROP'); + // [undefinedVal] + sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined); + }; + + const throwTypeErrorUnlessOptionalChain = (innerOptions: VisitOptions) => { + isOptionalChain ? processUndefined(innerOptions) : throwTypeError(innerOptions); + }; + const createProcessBuiltin = (valueName: string) => { const member = sb.context.builtins.getOnlyMember(valueName, nameValue); @@ -111,12 +123,12 @@ export class PropertyAccessExpressionCompiler extends NodeCompiler { + // [argsarr] + sb.emitOp(expr, 'DROP'); + // [] + sb.emitOp(expr, 'DROP'); + // [undefinedVal] + sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined); + }; + + const throwTypeErrorUnlessOptionalChain = (innerOptions: VisitOptions) => { + isOptionalChain ? processUndefined(innerOptions) : throwTypeError(innerOptions); + }; + const handleArguments = (innerOptions: VisitOptions) => { if (ts.isCallExpression(expression)) { // [argsarr] @@ -173,6 +187,67 @@ export class CallLikeHelper extends Helper { + // tslint:disable-next-line: no-loop-statement + for (let i = 0; i < n; i += 1) { + innerSb.emitOp(node, 'DROP'); + } + }; + + // Input: [...vals] + // Output: isNullOrUndefined ? [undefinedVal] : callCallback() + const callIfNotNullOrUndefined = ({ + innerOptions, + argsNum, + callback, + }: { + innerOptions: VisitOptions; + argsNum: number; + callback: () => void; + }) => { + sb.emitHelper( + expr, + optionsIn, + sb.helpers.if({ + condition: () => { + // [val, ...vals] + sb.emitOp(expr, 'DUP'); + // [isNull, ...vals] + sb.emitHelper(expr, optionsIn, sb.helpers.isNull); + }, + whenTrue: () => { + // [] + emitDropNTimes(sb, expr, argsNum); + // [undefinedVal] + sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined); + }, + whenFalse: () => { + sb.emitHelper( + expr, + optionsIn, + sb.helpers.if({ + condition: () => { + // [val, ...vals] + sb.emitOp(expr, 'DUP'); + // [isUndefined, ...vals] + sb.emitHelper(expr, optionsIn, sb.helpers.isUndefined); + }, + whenTrue: () => { + // [] + emitDropNTimes(sb, expr, argsNum); + // [undefinedVal] + sb.emitHelper(expr, innerOptions, sb.helpers.wrapUndefined); + }, + whenFalse: () => { + callback(); + }, + }), + ); + }, + }), + ); + }; + const superExpression = ts.isCallExpression(expression) ? tsUtils.expression.getExpression(expression) : undefined; if ( ts.isCallExpression(expression) && @@ -236,7 +311,13 @@ export class CallLikeHelper extends Helper sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })), + }) + : sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })); }; const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn)); @@ -474,7 +555,14 @@ export class CallLikeHelper extends Helper { // [val, objectVal, argsarr] sb.emitHelper(expr, innerInnerOptions, sb.helpers.getPropertyObjectProperty); - sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })); + isOptionalChain + ? // valIsNullOrUndefined ? [undefinedVal] : invokeCall() + callIfNotNullOrUndefined({ + innerOptions, + argsNum: 3, + callback: () => sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })), + }) + : sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })); }; const handleNumber = (innerInnerOptions: VisitOptions) => { @@ -494,7 +582,14 @@ export class CallLikeHelper extends Helper sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })), + }) + : sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: true })); }; // [argsarr, objectVal, thisVal] @@ -580,6 +675,54 @@ export class CallLikeHelper extends Helper { + sb.emitHelper(expr, optionsIn, sb.helpers.invokeCall({ bindThis: false })); + }; + + const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn)); + // [argsarr] + handleArguments(options); + // [objectVal, argsarr] + sb.visit(expr, options); + sb.emitHelper( + value, + options, + sb.helpers.forBuiltinType({ + type: valueType, + array: throwTypeError, + arrayStorage: throwTypeError, + boolean: throwTypeError, + buffer: throwTypeError, + null: throwTypeErrorUnlessOptionalChain, + number: throwTypeError, + object: processCall, + string: throwTypeError, + symbol: throwTypeError, + undefined: throwTypeErrorUnlessOptionalChain, + map: throwTypeError, + mapStorage: throwTypeError, + set: throwTypeError, + setStorage: throwTypeError, + error: throwTypeError, + forwardValue: throwTypeError, + iteratorResult: throwTypeError, + iterable: throwTypeError, + iterableIterator: throwTypeError, + transaction: throwTypeError, + output: throwTypeError, + attribute: throwTypeError, + input: throwTypeError, + account: throwTypeError, + asset: throwTypeError, + contract: throwTypeError, + header: throwTypeError, + block: throwTypeError, + }), + ); } else { const options = sb.pushValueOptions(sb.noSetValueOptions(optionsIn)); // [argsarr] diff --git a/packages/neo-one-typescript-concatenator/src/__data__/snippets/import/entry.ts b/packages/neo-one-typescript-concatenator/src/__data__/snippets/import/entry.ts index 219556734a..1d45a22295 100644 --- a/packages/neo-one-typescript-concatenator/src/__data__/snippets/import/entry.ts +++ b/packages/neo-one-typescript-concatenator/src/__data__/snippets/import/entry.ts @@ -3,6 +3,7 @@ import * as bar from './bar'; import baz from './baz'; import { foo } from './foo'; import { Address, foo as foo2, Foo2SmartContract, SmartContract } from './foo2'; +import type { MyType } from './onlyTypes' import incrementValue, { value } from './qux'; import { FooType } from './type'; @@ -83,3 +84,5 @@ export { fizz } from './varDecl'; // Namespace export export * as foo5 from './foo5'; export * as somethingElse from './foo5'; +// Type-only export +export type { MyType }; diff --git a/packages/neo-one-typescript-concatenator/src/__data__/snippets/import/onlyTypes.ts b/packages/neo-one-typescript-concatenator/src/__data__/snippets/import/onlyTypes.ts new file mode 100644 index 0000000000..eeab242b62 --- /dev/null +++ b/packages/neo-one-typescript-concatenator/src/__data__/snippets/import/onlyTypes.ts @@ -0,0 +1,3 @@ +type MyType = string; +// tslint:disable-next-line: export-name +export type { MyType }; diff --git a/packages/neo-one-typescript-concatenator/src/__tests__/__snapshots__/concatenate.test.ts.snap b/packages/neo-one-typescript-concatenator/src/__tests__/__snapshots__/concatenate.test.ts.snap index e1ec6749f3..478ac586b9 100644 --- a/packages/neo-one-typescript-concatenator/src/__tests__/__snapshots__/concatenate.test.ts.snap +++ b/packages/neo-one-typescript-concatenator/src/__tests__/__snapshots__/concatenate.test.ts.snap @@ -22,21 +22,22 @@ class FooType { const fooType = 'fooType'; // tslint:disable-next-line no-let let valc1 = 0; -function value44() { +function value48() { return valc1; } -function default41() { +function default45() { valc1 += 1; } -const default33 = 'baz'; +type MyType = string; +const default35 = 'baz'; // tslint:disable-next-line no-let let valc2 = 0; const x = 3; -const value48 = () => valc2; +const value52 = () => valc2; const incrementValue = () => { valc2 += 1; }; -const bar = { value: value48, incrementValue: incrementValue, x: x }; +const bar = { value: value52, incrementValue: incrementValue, x: x }; if (foo !== 'foo') { throw 'Failure'; } @@ -50,17 +51,17 @@ bar.incrementValue(); if (bar.value() !== 1) { throw 'Failure'; } -if (value44() !== 0) { +if (value48() !== 0) { throw 'Failure'; } -default41(); -if (value44() !== 1) { +default45(); +if (value48() !== 1) { throw 'Failure'; } if (bar.x !== 3) { throw 'Failure'; } -if (default33 !== 'baz') { +if (default35 !== 'baz') { throw 'Failure'; } // tslint:disable-next-line export-name @@ -81,13 +82,14 @@ export { fooSC as fooSC }; export { fooSC as barSC }; export { bar }; export { bar as bar2 }; -export { default33 as baz }; -export { default33 as baz2 }; +export { default35 as baz }; +export { default35 as baz2 }; export { FooType as FooType, fooType as fooType }; export { Address as Address, foo as foo, SmartContract as SmartContract }; export { SmartContract as SmartContract2Level, default11 as FooSmartContract2Level, foo as foo2level, Address as Address2Level }; export { fizz as fizz }; export const foo5 = { one: one, two: two, func: func }; export const somethingElse = { one: one, two: two, func: func }; +export { MyType as MyType }; " `;