diff --git a/src/typeChecker/__tests__/source4TypedAnyChecker.test.ts b/src/typeChecker/__tests__/source4TypedAnyChecker.test.ts new file mode 100644 index 000000000..54a179d96 --- /dev/null +++ b/src/typeChecker/__tests__/source4TypedAnyChecker.test.ts @@ -0,0 +1,306 @@ +import { parseError } from '../../index' +import { SourceTypedParser } from '../../parser/source/typed' +import { Chapter, type LanguageOptions, Variant } from '../../types' +import { mockContext } from '../../utils/testing/mocks' + +const parser = new SourceTypedParser(Chapter.SOURCE_4, Variant.TYPED) + +describe('Any checker tests', () => { + test('disallow any type in a variable declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInVariables'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('const x = 4;', localContext) + expect(parseError(localContext.errors)).toEqual( + 'Line 1: Usage of "any" in variable declaration is not allowed.' + ) + }) + + test('allow any type in a variable declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInVariables'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('let x: any = 4;', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in a variable declaration, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInVariables'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('let x: number = 4;', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('disallow any type in function parameter', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('function f(x: any) { return x; }', localContext) + expect(parseError(localContext.errors)).toEqual( + 'Line 1: Usage of "any" in function parameter is not allowed.' + ) + }) + + test('allow any type in function parameter', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('function f(x: any) { return x; }', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in function parameter, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('function f(x: number) { return x; }', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('disallow any type in function return type', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInReturnType'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('function g(): any { return 4; }', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in function return type', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInReturnType'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('function g(): any { return 4; }', localContext) + expect(parseError(localContext.errors)).toEqual( + 'Line 1: Usage of "any" in function return type is not allowed.' + ) + }) + + test('allow any type in function return type, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInReturnType'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('function g(): number { return 4; }', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('disallow any type in lambda parameter', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('const h = (x: any) => x + 1;', localContext) + expect(parseError(localContext.errors)).toEqual( + 'Line 1: Usage of "any" in arrow function parameter is not allowed.' + ) + }) + + test('allow any type in lambda parameter', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('const h = (x: any) => x + 1;', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in lambda parameter, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('const h = (x: number) => x + 1;', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('disallow any type in nested lambda', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('const f = (x: number) => (y: any) => x + y;', localContext) + expect(parseError(localContext.errors)).toEqual( + 'Line 1: Usage of "any" in arrow function parameter is not allowed.' + ) + }) + + test('allow any type in nested lambda', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('const f = (x: number) => (y: any) => x + y;', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in nested lambda, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse('const f = (x: number) => (y: number) => x + y;', localContext) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in nested function', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) { + function g(y: any) { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('disallow any type in nested function', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) { + function g(y: any) { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual( + 'Line 3: Usage of "any" in function parameter is not allowed.' + ) + }) + + test('allow any type in nested function, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) : (y: number) => number { + function g(y: number) { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in type annotation parameters', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInTypeAnnotationParameters'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) : (y: any) => number { + function g(y: number) { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('disallow any type in type annotation parameters', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInTypeAnnotationParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) : (y: any) => number { + function g(y: number) { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual( + 'Line 2: Usage of "any" in type annotation\'s function parameter is not allowed.' + ) + }) + + test('disallow any type in type annotation parameters, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInTypeAnnotationParameters'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) : (y: number) => number { + function g(y: number) { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('allow any type in type annotation return type', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInTypeAnnotationReturnType'] = 'true' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) { + function g(y: number) : any { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual('') + }) + + test('disallow any type in type annotation return type', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInTypeAnnotationReturnType'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) : (y: number) => any { + function g(y: number) : number { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual( + 'Line 2: Usage of "any" in type annotation\'s function return type is not allowed.' + ) + }) + + test('disallow any type in type annotation return type, correct declaration', () => { + const languageOptions: LanguageOptions = {} + languageOptions['typedAllowAnyInTypeAnnotationReturnType'] = 'false' + const localContext = mockContext(Chapter.SOURCE_4, Variant.TYPED, languageOptions) + parser.parse( + ` + function f(x: number) : (y: number) => number { + function g(y: number) : number { + return x + y; + } + return g; + } + `, + localContext + ) + expect(parseError(localContext.errors)).toEqual('') + }) +}) diff --git a/src/typeChecker/__tests__/source4TypedModules.test.ts b/src/typeChecker/__tests__/source4TypedModules.test.ts index 23dd87246..ad42c8c79 100644 --- a/src/typeChecker/__tests__/source4TypedModules.test.ts +++ b/src/typeChecker/__tests__/source4TypedModules.test.ts @@ -1,7 +1,7 @@ -import { mockContext } from '../../utils/testing/mocks' -import { Chapter, Variant } from '../../types' -import { parse } from '../../parser/parser' import { parseError } from '../../index' +import { parse } from '../../parser/parser' +import { Chapter, Variant } from '../../types' +import { mockContext } from '../../utils/testing/mocks' function getContext() { const context = mockContext(Chapter.SOURCE_4, Variant.TYPED) @@ -14,6 +14,8 @@ function getContext() { class Test1 {} class Test2 {} class Test3 {} + type Test4 = (arg: Test1) => Test2; + const Test4 = (arg: Test1) => Test2; `, x: 'const x: string = "hello"', y: 'const y: number = 42', @@ -179,7 +181,7 @@ describe('Typed module tests', () => { const a: string = functionError(10); ` expect(testParseError(code)).toMatchInlineSnapshot( - `"Line 6: Type 'number' is not assignable to type 'string'."` + `"Line 8: Type 'number' is not assignable to type 'string'."` ) }) @@ -261,4 +263,23 @@ describe('Typed module tests', () => { ) }) }) + + /* TEST CASES FOR THE 'Test4' TYPE */ + it('should allow calling Test4 with a valid Test1 object', () => { + const code = ` + import { test2 } from 'exampleModule'; + const result: Test4 = (arg: Test1) => test2; + ` + expect(testParseError(code)).toMatchInlineSnapshot(`""`) + }) + + it('should error when calling Test4 with a string argument', () => { + const code = ` + import { test1 } from 'exampleModule'; + const result: Test4 = (arg: Test1) => test1; + ` + expect(testParseError(code)).toMatchInlineSnapshot( + `"Line 3: Type '(Test1) => Test1' is not assignable to type 'Test4'."` + ) + }) })