diff --git a/linter-4.2/.gitignore b/linter-4.2/.gitignore index 3ded455fc811af5ac325bb4ed532facefecd86a4..4797db793b998b8c2ea246115378791d451072f7 100644 --- a/linter-4.2/.gitignore +++ b/linter-4.2/.gitignore @@ -2,6 +2,7 @@ build dist bundle node_modules +cookbook_convertor/md *.tsbuildinfo .vs .vscode diff --git a/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts b/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts index 201123993749eca70c982c7d3a1300d99e3ec08c..65279a304b149cf2bebff17f664eddb54044ca57 100644 --- a/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts +++ b/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts @@ -36,7 +36,7 @@ const COPYRIGHT_HEADER = "/* \n\ const CODE_PROLOGUE = "export const cookBookMsg: string[] = [];\n\ export const cookBookTag: string[] = [];\n\ \n\ -for( let i = 0; i < 144; i++) {\n\ +for( let i = 0; i < 148; i++) {\n\ cookBookMsg[ i ] = '';\n\ }\n\ "; @@ -372,7 +372,7 @@ function makeOk(): string { needHeader(); console.error( ">>>makeOK HDR>>>: " + doc_lines[ _line ] ); - if( !doc_lines[_line].startsWith(CB_OK) ) { + if( _line >= doc_lines.length || !doc_lines[_line].startsWith(CB_OK) ) { return ""; } _line++; _line++; // skip underline diff --git a/linter-4.2/docs/rules/recipe1.md b/linter-4.2/docs/rules/recipe1.md index 317d04c6bd75c5d7c80e2e19a7553595833174ac..cda73e56f251fa409b819241734af7ca03fc3039 100644 --- a/linter-4.2/docs/rules/recipe1.md +++ b/linter-4.2/docs/rules/recipe1.md @@ -27,7 +27,7 @@ data by numeric indices. ``` class X { - public name: number + public name: number = 0 } let x = {name: 1} console.log(x.name) diff --git a/linter-4.2/docs/rules/recipe10.md b/linter-4.2/docs/rules/recipe10.md deleted file mode 100644 index cfaa4830f4010e3ab21ac59d8e31d53eb69c9a41..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe10.md +++ /dev/null @@ -1,31 +0,0 @@ -# ``bigint`` is not a builtin type, suffix ``n`` for numeric literals is not supported - -Rule ``arkts-no-n-suffix`` - -**Severity: error** - -ArkTS supports ``bigint`` as a part of the standard library, not as a builtin -type. ``n`` suffix for numeric literals is not supported, ``BigInt`` factory -function can be used to produce values of ``bigint`` type. - - -## TypeScript - - -``` - - let a: bigint = 1n - -``` - -## ArkTS - - -``` - - let a = BigInt(1) - let b: bigint = BigInt(2) - -``` - - diff --git a/linter-4.2/docs/rules/recipe102.md b/linter-4.2/docs/rules/recipe102.md index 6776df1b8814cd78ee187c8c4ead007dc4775ebe..598f43cccdf02f0b583d10487553c938764b2042 100644 --- a/linter-4.2/docs/rules/recipe102.md +++ b/linter-4.2/docs/rules/recipe102.md @@ -1,10 +1,10 @@ # Interface declarations (extends same property) -Rule ``arkts-no-extend-same-property`` +Rule ``arkts-no-extend-same-prop`` **Severity: error** -In TypeScript, an interface that extends two other interfaces with the same method, +In TypeScript, an interface that extends two other interfaces with the same method must declare that method with a combined result type. It is not allowed in ArkTS because |LANG| does not allow an interface to contain two methods with signatures that are not distinguishable, e.g., two methods that have the same diff --git a/linter-4.2/docs/rules/recipe105.md b/linter-4.2/docs/rules/recipe105.md index e7e9d47bd4ddee7855a9b3261376434ed844ee85..f68c49d03a953af0e1023b6879547fc08d7b205b 100644 --- a/linter-4.2/docs/rules/recipe105.md +++ b/linter-4.2/docs/rules/recipe105.md @@ -4,11 +4,12 @@ Rule ``arkts-no-prop-existence-check`` **Severity: error** -ArkTS requires that object layout is determined in compile-time and cannot +ArkTS requires that object layout is determined at compile time and cannot be changed at runtime. There for no runtime property-based checks are supported. If you need to do a type cast, use ``as`` operator and use desired properties -and methods. If some property doesn't exist then an attempt to reference it -will result in a compile-time error. +and methods. +If some property does not exist, then an attempt to refer to it results +in a compile-time error. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe109.md b/linter-4.2/docs/rules/recipe109.md index 05dd754c2161ecbca46bdb90943e9049fdad2cfb..ec6ef7f38e7c7019eccf4a7c0817a67ae59aa55d 100644 --- a/linter-4.2/docs/rules/recipe109.md +++ b/linter-4.2/docs/rules/recipe109.md @@ -4,10 +4,10 @@ Rule ``arkts-no-dyn-prop-decl`` **Severity: error** -ArkTS does not support dynamic property declaration. All object properties must -be declared immediately in the class. While it can be replaced with an array -of objects, it is still better to adhere to the static language paradigm and -declare fields, their names and types explicitly. +ArkTS does not support dynamic property declaration. All object properties +must be declared immediately in the class. While it can be replaced with an +array of objects, it is still better to adhere to the static language paradigm +and declare fields, their names and types explicitly. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe113.md b/linter-4.2/docs/rules/recipe113.md index 26465b677232b037a657f738a67caa80eca601fc..26a1aa7f5a1330bd0c2b2efcf3ed59ce2b6de924 100644 --- a/linter-4.2/docs/rules/recipe113.md +++ b/linter-4.2/docs/rules/recipe113.md @@ -4,8 +4,8 @@ Rule ``arkts-no-enum-merging`` **Severity: error** -ArkTS does not support merging declratations for ``enum``. -The declaration of each ``enum`` must be kept compact in the code base. +ArkTS does not support merging declratations for ``enum``. The declaration +of each ``enum`` must be kept compact in the code base. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe115.md b/linter-4.2/docs/rules/recipe115.md deleted file mode 100644 index ee73f68fd25bfc22657de9f15ea50dd826f62462..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe115.md +++ /dev/null @@ -1,21 +0,0 @@ -# Scripts and modules - -Rule ``arkts-no-scripts`` - -**Severity: error** - -In general, scripts and modules in ArkTS are very close to TypeScript. -Differences are described in separate recipes. - - -## See also - -- Recipe 118: Special import type declarations are not supported (``arkts-no-special-imports``) -- Recipe 119: Importing a module for side-effects only is not supported (``arkts-no-side-effects-imports``) -- Recipe 120: ``import default as ...`` is not supported (``arkts-no-import-default-as``) -- Recipe 121: ``require`` and ``import`` assignment are not supported (``arkts-no-require``) -- Recipe 124: Export list declaration is not supported (``arkts-no-export-list-decl``) -- Recipe 125: Re-exporting is supported with restrictions (``arkts-limited-reexport``) -- Recipe 126: ``export = ...`` assignment is not supported (``arkts-no-export-assignment``) - - diff --git a/linter-4.2/docs/rules/recipe118.md b/linter-4.2/docs/rules/recipe118.md index 0041552fdb013d8acf3a5c3078f2ebc668725250..f625bc3e982353617c9dd28f7c6d67ff886e1e6d 100644 --- a/linter-4.2/docs/rules/recipe118.md +++ b/linter-4.2/docs/rules/recipe118.md @@ -14,10 +14,10 @@ Use ordinary import instead. ``` // Re-using the same import - import { APIResponseType } from "./api" + import { APIResponseType } from "api" // Explicitly use import type - import type { APIResponseType } from "./api" + import type { APIResponseType } from "api" ``` @@ -26,7 +26,7 @@ Use ordinary import instead. ``` - import { APIResponseType } from "./api" + import { APIResponseType } from "api" ``` diff --git a/linter-4.2/docs/rules/recipe121.md b/linter-4.2/docs/rules/recipe121.md index ef4631c12d6b3523a67e1a709ac545268a8bcc8d..ce8d38e80089c630c5e0b50d18d1fd514abfad48 100644 --- a/linter-4.2/docs/rules/recipe121.md +++ b/linter-4.2/docs/rules/recipe121.md @@ -4,8 +4,9 @@ Rule ``arkts-no-require`` **Severity: error** -ArkTS does not support importing via ``require``. ``import`` assignment are -not supported either. Use regular ``import`` instead. +ArkTS does not support importing via ``require``. +``import`` assignments are not supported either. +Use regular ``import`` instead. ## TypeScript @@ -31,3 +32,4 @@ not supported either. Use regular ``import`` instead. - Recipe 126: ``export = ...`` assignment is not supported (``arkts-no-export-assignment``) + diff --git a/linter-4.2/docs/rules/recipe123.md b/linter-4.2/docs/rules/recipe123.md deleted file mode 100644 index 93667373868f3ab91ac6ca468f8d5bd25ec90c65..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe123.md +++ /dev/null @@ -1,61 +0,0 @@ -# Renaming in export declarations is not supported - -Rule ``arkts-no-export-renaming`` - -**Severity: error** - -ArkTS does not support renaming in export declarations. Similar effect -can be achieved through setting an alias for the exported entity. - - -## TypeScript - - -``` - - // file1.ts - class MyClass { - // ... - } - - export { MyClass as RenamedClass } - - // file2.ts - import { RenamedClass } from "./file1" - - function main(): void { - const myObject = new RenamedClass() - // ... - } - -``` - -## ArkTS - - -``` - - // module1 - class MyClass { - // ... - } - - export type RenamedClass = MyClass - - // module2 - import { RenamedClass } from "./module1" - - function main(): void { - const myObject = new RenamedClass() - // ... - } - -``` - -## See also - -- Recipe 124: Export list declaration is not supported (``arkts-no-export-list-decl``) -- Recipe 125: Re-exporting is not supported (``arkts-no-reexport``) -- Recipe 126: ``export = ...`` assignment is not supported (``arkts-no-export-assignment``) - - diff --git a/linter-4.2/docs/rules/recipe124.md b/linter-4.2/docs/rules/recipe124.md deleted file mode 100644 index b1a80dc42b5afd5d926351050b452986d9c7161c..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe124.md +++ /dev/null @@ -1,39 +0,0 @@ -# Export list declaration is not supported - -Rule ``arkts-no-export-list-decl`` - -**Severity: error** - -ArkTS does not support syntax of export list declarations. All exported -entities must be explicitly annotated with the ``export`` keyword. - - -## TypeScript - - -``` - - export { x } - export { x } from "mod" - export { x, y as b, z as c } - -``` - -## ArkTS - - -``` - - let x = 1 - class MyClass {} - export let y = x, z: number = 2 - export RenamedClass = MyClass - -``` - -## See also - -- Recipe 125: Re-exporting is supported with restrictions (``arkts-limited-reexport``) -- Recipe 126: ``export = ...`` assignment is not supported (``arkts-no-export-assignment``) - - diff --git a/linter-4.2/docs/rules/recipe125.md b/linter-4.2/docs/rules/recipe125.md index 4aa7cec5d9d73203e8d1f8c54f74d4353899e904..50109740e0f058ef86ad437bfa227afd14ff7ef7 100644 --- a/linter-4.2/docs/rules/recipe125.md +++ b/linter-4.2/docs/rules/recipe125.md @@ -54,7 +54,6 @@ Other syntax flavors like ``export * as ...`` are not supported. ## See also -- Recipe 124: Export list declaration is not supported (``arkts-no-export-list-decl``) - Recipe 126: ``export = ...`` assignment is not supported (``arkts-no-export-assignment``) diff --git a/linter-4.2/docs/rules/recipe126.md b/linter-4.2/docs/rules/recipe126.md index 59a9551cae1c65460da88d2b6b098b6c597a6f74..5e4f7f9afa9dbdd4bb0f1e88fcc5f7c2cd03ca0a 100644 --- a/linter-4.2/docs/rules/recipe126.md +++ b/linter-4.2/docs/rules/recipe126.md @@ -49,7 +49,6 @@ Use regular ``export`` / ``import`` instead. ## See also - Recipe 121: ``require`` and ``import`` assignment are not supported (``arkts-no-require``) -- Recipe 124: Export list declaration is not supported (``arkts-no-export-list-decl``) - Recipe 125: Re-exporting is supported with restrictions (``arkts-limited-reexport``) diff --git a/linter-4.2/docs/rules/recipe128.md b/linter-4.2/docs/rules/recipe128.md index 9a5ccebd49091bd5476bf510ae314d8579f3c80d..ddba6753b7c5499be4977aeec2bc5810c359c4f4 100644 --- a/linter-4.2/docs/rules/recipe128.md +++ b/linter-4.2/docs/rules/recipe128.md @@ -25,7 +25,7 @@ own mechanisms for interoperating with JavaScript. ``` // Import what you need from the original module - import { normalize } from "../someModule" + import { normalize } from "someModule" ``` diff --git a/linter-4.2/docs/rules/recipe129.md b/linter-4.2/docs/rules/recipe129.md index fa5e7dc9e1e5e632850aa9eddb9e779dd63643d0..6d41a927b562fa855704b4ea0b2f5cb3dbe1a4ab 100644 --- a/linter-4.2/docs/rules/recipe129.md +++ b/linter-4.2/docs/rules/recipe129.md @@ -4,8 +4,9 @@ Rule ``arkts-no-module-wildcards`` **Severity: error** -ArkTS does not supported wildcards in module names because in |LANG|, import -is a compile-time, not a runtime feature. Use ordinary export syntax instead. +ArkTS does not support wildcards in module names because in the language +import is a compile-time, not a runtime feature. +Use ordinary export syntax instead. ## TypeScript @@ -13,11 +14,31 @@ is a compile-time, not a runtime feature. Use ordinary export syntax instead. ``` + // Declaration: declare module "*!text" { const content: string export default content } + // Consuming code: + import fileContent from "some.txt!text" + +``` + +## ArkTS + + +``` + + // Declaration: + declare namespace N { + function foo(x: number): number + } + + // Consuming code: + import * from "module" + console.log("N.foo called: ", N.foo(42)) + ``` ## See also diff --git a/linter-4.2/docs/rules/recipe130.md b/linter-4.2/docs/rules/recipe130.md index da2aada018a1563dd2c1f404b017a3434b285fa7..b0f094ff9932b1e969f74158dd7d7047033b6981 100644 --- a/linter-4.2/docs/rules/recipe130.md +++ b/linter-4.2/docs/rules/recipe130.md @@ -4,10 +4,10 @@ Rule ``arkts-no-umd`` **Severity: error** -ArkTS does not support universal module definitions (UMD) because in |LANG| -there is no concept of "script" (as opposed to "module"). Besides, in ArkTS -import is a compile-time, not a runtime feature. Use ordinary syntax for -``export`` and ``import`` instead. +ArkTS does not support universal module definitions (UMD) because in the +language there is no concept of "script" (as opposed to "module"). +Besides, in ArkTS import is a compile-time, not a runtime feature. +Use ordinary syntax for ``export`` and ``import`` instead. ## TypeScript @@ -35,7 +35,7 @@ import is a compile-time, not a runtime feature. Use ordinary syntax for } // in program - import { mathLib } from "./math-lib" + import { mathLib } from "math-lib" mathLib.isPrime(2) ``` diff --git a/linter-4.2/docs/rules/recipe131.md b/linter-4.2/docs/rules/recipe131.md index c9f6e9c22c3de7ef824bd6054afee845478aceed..9bd40dd8fc1ee141026d8e9a980b2cddb1d6ff65 100644 --- a/linter-4.2/docs/rules/recipe131.md +++ b/linter-4.2/docs/rules/recipe131.md @@ -4,8 +4,8 @@ Rule ``arkts-no-js-extension`` **Severity: error** -ArkTS does not allow to use ``.js`` extension in module identifiers because it -has its own mechanisms for interoperating with JavaScript. +ArkTS does not allow using ``.js`` extension in module identifiers because +it has its own mechanisms for interoperating with JavaScript. ## TypeScript @@ -13,7 +13,7 @@ has its own mechanisms for interoperating with JavaScript. ``` - import { something } from "./module.js" + import { something } from "module.js" ``` @@ -22,7 +22,7 @@ has its own mechanisms for interoperating with JavaScript. ``` - import { something } from "./module" + import { something } from "module" ``` diff --git a/linter-4.2/docs/rules/recipe132.md b/linter-4.2/docs/rules/recipe132.md index c382996386e72b8292be33f3544b7e4c42f0b1ad..517a1c5a669a0438de0a13885b7fd156f171170c 100644 --- a/linter-4.2/docs/rules/recipe132.md +++ b/linter-4.2/docs/rules/recipe132.md @@ -26,6 +26,23 @@ to the statically typing. ``` +## ArkTS + + +``` + + class CustomError extends Error { + constructor(message?: string) { + // Call parent's constructor, inheritance chain is static and + // cannot be modified in runtime + super(message) + console.log(this instanceof Error) // true + } + } + let ce = new CustomError() + +``` + ## See also - Recipe 136: Prototype assignment is not supported (``arkts-no-prototype-assignment``) diff --git a/linter-4.2/docs/rules/recipe133.md b/linter-4.2/docs/rules/recipe133.md index 222bfdee09033650658d89ce1087c5782bead853..b9c5863004a6c1f026e864c67bb4fc4823256374 100644 --- a/linter-4.2/docs/rules/recipe133.md +++ b/linter-4.2/docs/rules/recipe133.md @@ -5,8 +5,8 @@ Rule ``arkts-no-runtime-import`` **Severity: error** ArkTS does not support such "runtime" import expressions as ``await import...`` -because in ArkTS import is a compile-time, not a runtime feature. Use regular -import syntax instead. +because in the language import is a compile-time, not a runtime feature. +Use regular import syntax instead. ## TypeScript @@ -14,7 +14,7 @@ import syntax instead. ``` - const zipUtil = await import("./utils/create-zip-file") + const zipUtil = await import("utils/create-zip-file") ``` @@ -23,7 +23,7 @@ import syntax instead. ``` - import { zipUtil } from "./utils/create-zip-file" + import { zipUtil } from "utils/create-zip-file" ``` diff --git a/linter-4.2/docs/rules/recipe134.md b/linter-4.2/docs/rules/recipe134.md index 3286e1cbfddd933ef19ca7e3a1b7a3ecbe25ec4b..60916e809ec50d5be832bd2fd65823603b142130 100644 --- a/linter-4.2/docs/rules/recipe134.md +++ b/linter-4.2/docs/rules/recipe134.md @@ -5,8 +5,8 @@ Rule ``arkts-no-definite-assignment`` **Severity: error** ArkTS does not support definite assignment assertions ``let v!: T`` because -they are considered an excessive compiler hint. Use declaration with -initialization instead. +they are considered an excessive compiler hint. +Use declaration with initialization instead. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe135.md b/linter-4.2/docs/rules/recipe135.md index 545a49b9b8351cd67693e04219743eea6bbb4f07..df981900ae4987a0ded2fb70884e61c5b54f4a7b 100644 --- a/linter-4.2/docs/rules/recipe135.md +++ b/linter-4.2/docs/rules/recipe135.md @@ -4,9 +4,9 @@ Rule ``arkts-no-iife`` **Severity: error** -ArkTS does not support IIFEs as namespace declarations because in |LANG|, -anonymous functions cannot serve as namespaces. Use regular syntax for -namespaces instead. +ArkTS does not support IIFEs as namespace declarations because anonymous +functions in the language cannot serve as namespaces. +Use regular syntax for namespaces instead. ## TypeScript @@ -15,9 +15,10 @@ namespaces instead. ``` var C = (function() { - function C(n) { - this.p = n + function C(n: number) { + this.p = n // Compile-time error only with noImplicitThis } + C.staticProperty = 0 return C })() C.staticProperty = 1 diff --git a/linter-4.2/docs/rules/recipe136.md b/linter-4.2/docs/rules/recipe136.md index eb2ada7062458b5bd9243fad473283a77161429c..231fe3003e97c59726c601ffb02bf0301104bb5b 100644 --- a/linter-4.2/docs/rules/recipe136.md +++ b/linter-4.2/docs/rules/recipe136.md @@ -6,7 +6,8 @@ Rule ``arkts-no-prototype-assignment`` ArkTS does not support prototype assignment because there is no concept of runtime prototype inheritance in the language. This feature is considered not -applicable to the static typing. +applicable to the static typing. Mechanism of classes and / or interfaces +should be used instead to statically "combine" methods to data together. ## TypeScript @@ -14,8 +15,8 @@ applicable to the static typing. ``` - var C = function(p) { - this.p = p + var C = function(p: number) { + this.p = p // Compile-time error only with noImplicitThis } C.prototype = { @@ -24,8 +25,25 @@ applicable to the static typing. } } - C.prototype.q = function(r) { - return this.p === r + C.prototype.q = function(r: number) { + return this.p == r + } + +``` + +## ArkTS + + +``` + + class C { + p: number = 0 + m() { + console.log(this.p) + } + q(r: number) { + return this.p == r + } } ``` diff --git a/linter-4.2/docs/rules/recipe137.md b/linter-4.2/docs/rules/recipe137.md index 588cc70f46e523414ec8c99dc61aad374f963c14..5cb8d691d69879fb83eece870004ac011d61267b 100644 --- a/linter-4.2/docs/rules/recipe137.md +++ b/linter-4.2/docs/rules/recipe137.md @@ -30,7 +30,7 @@ objects with dynamically changed layout are not supported. export let abc : number = 0 // file2 - import * as M from "../file1" + import * as M from "file1" M.abc = 200 diff --git a/linter-4.2/docs/rules/recipe138.md b/linter-4.2/docs/rules/recipe138.md index f97ec50ae8cf49b9c6d2ed321ed69e7603f3f779..8435779396a8484f01502ac97d512408b8dd6323 100644 --- a/linter-4.2/docs/rules/recipe138.md +++ b/linter-4.2/docs/rules/recipe138.md @@ -8,6 +8,9 @@ Currently ArkTS does not support utility types from TypeScript extensions to the standard library (``Omit``, ``Pick``, etc.). Exceptions are: ``Partial``, ``Record``. +For Record type, the type of an indexing expression *rec[index]* is +of * *V | undefined* type. + ## TypeScript @@ -22,6 +25,21 @@ standard library (``Omit``, ``Pick``, etc.). Exceptions are: ``Partial``, type QuantumPerson = Omit + let persons : Record = { + "Alice": { + name: "Alice", + age: 32, + location: "Shanghai" + }, + "Bob": { + name: "Bob", + age: 48, + location: "New York" + } + } + console.log(persons["Bob"].age) + console.log(persons["Rob"].age) // Runtime exception + ``` ## ArkTS @@ -30,14 +48,34 @@ standard library (``Omit``, ``Pick``, etc.). Exceptions are: ``Partial``, ``` class Person { - name: string - age: number - location: string + name: string = "" + age: number = 0 + location: string = "" } class QuantumPerson { - name: string - age: number + name: string = "" + age: number = 0 + } + + type OptionalPerson = Person | undefined + let persons : Record = { + // Or: + // let persons : Record = { + "Alice": { + name: "Alice", + age: 32, + location: "Shanghai" + }, + "Bob": { + name: "Bob", + age: 48, + location: "New York" + } + } + console.log(persons["Bob"]!.age) + if (persons["Rob"]) { // Explicit value check, no runtime exception + console.log(persons["Rob"].age) } ``` diff --git a/linter-4.2/docs/rules/recipe139.md b/linter-4.2/docs/rules/recipe139.md index 5b27d1ebfa1009bd3986a339da8172aec5b19fef..70fd6896612202816c9229291c7c470d499e8e00 100644 --- a/linter-4.2/docs/rules/recipe139.md +++ b/linter-4.2/docs/rules/recipe139.md @@ -49,15 +49,17 @@ this rule and their layout cannot be changed in runtime. // ... } - function readImage( + async function readImage( path: string, callback: (err: Error, image: MyImage) => void ) : Promise { - // async implementation + // In real world, the implementation is more complex, + // involving real network / DB logic, etc. + return await new MyImage() } function readImageSync(path: string) : MyImage { - // sync implementation + return new MyImage() } ``` diff --git a/linter-4.2/docs/rules/recipe140.md b/linter-4.2/docs/rules/recipe140.md index ae347c437cfa8c457bbbcce9c63223fe6c4df2c8..febf6d05552c1ae96ae2e94f77d28fa40bc05810 100644 --- a/linter-4.2/docs/rules/recipe140.md +++ b/linter-4.2/docs/rules/recipe140.md @@ -4,12 +4,12 @@ Rule ``arkts-no-func-apply-bind-call`` **Severity: error** -ArkTS does not allow to use standard library functions ``Function.apply``, -``Function.bind``, ``Function.call``. These APIs are needed in the standard +ArkTS does not allow using standard library functions ``Function.apply``, +``Function.bind`` and ``Function.call``. These APIs are needed in the standard library to explicitly set ``this`` parameter for the called function. -In ArkTS semantics of ``this`` is restricted to the conventional OOP style, -and usage of ``this`` in stand-alone functions is prohibited. Thus these -functions are excessive. +In ArkTS the semantics of ``this`` is restricted to the conventional OOP +style, and the usage of ``this`` in stand-alone functions is prohibited. +Thus these functions are excessive. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe141.md b/linter-4.2/docs/rules/recipe141.md index 1e16d75ad29c73250441a8c0d6a249a1f212e658..a772b5388d1033d6e2bd8711a7bd3f1956b60ec0 100644 --- a/linter-4.2/docs/rules/recipe141.md +++ b/linter-4.2/docs/rules/recipe141.md @@ -4,7 +4,7 @@ Rule ``arkts-no-readonly-params`` **Severity: error** -Currently ArkTS supports ``readonly`` for properties, but not for parameters. +Currently, ArkTS supports ``readonly`` for properties, but not for parameters. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe142.md b/linter-4.2/docs/rules/recipe142.md index cca78dc76637bdb0eda7fa5750cb6a3bc51bc497..7cef054e7cba81bc32feedebfb1e795d46ccd299 100644 --- a/linter-4.2/docs/rules/recipe142.md +++ b/linter-4.2/docs/rules/recipe142.md @@ -37,7 +37,7 @@ does not support literal types. let y : number[] = [10, 20] class Label { - text : string + text : string = "" } // Type 'Label': diff --git a/linter-4.2/docs/rules/recipe143.md b/linter-4.2/docs/rules/recipe143.md index 946b66efabc033db1d1035ab972de036f8cd2872..444ceadb8736ddc745b7ad0caa2763d6fb1560f1 100644 --- a/linter-4.2/docs/rules/recipe143.md +++ b/linter-4.2/docs/rules/recipe143.md @@ -4,7 +4,7 @@ Rule ``arkts-no-import-assertions`` **Severity: error** -ArkTS does not support import assertions because in |LANG|, import is a +ArkTS does not support import assertions because in the language import is a compile-time, not a runtime feature. So asserting correctness of imported APIs in runtime does not make sense for the statically typed language. Use ordinary ``import`` syntax instead. @@ -15,7 +15,7 @@ in runtime does not make sense for the statically typed language. Use ordinary ``` - import { obj } from "./something.json" assert { type: "json" } + import { obj } from "something.json" assert { type: "json" } ``` @@ -25,7 +25,7 @@ in runtime does not make sense for the statically typed language. Use ordinary ``` // Correctness of importing T will be checked in compile-time: - import { something } from "./module" + import { something } from "module" ``` diff --git a/linter-4.2/docs/rules/recipe144.md b/linter-4.2/docs/rules/recipe144.md index 7a2ed2363221289e447529fe31ec777d25d39e44..556747f0cfbfa25f1e975a1d86b060ce883d7b92 100644 --- a/linter-4.2/docs/rules/recipe144.md +++ b/linter-4.2/docs/rules/recipe144.md @@ -4,10 +4,10 @@ Rule ``arkts-limited-stdlib`` **Severity: error** -ArkTS does not allow usage of some APIs from the TypeScript/JavaScript standard library. -The most part of the restricted APIs relates to manipulating objects in -dynamic manner, which is not compatible with the static typing. Following APIs -are prohibited from usage: +ArkTS does not allow using some APIs from the TypeScript/JavaScript standard library. +The most part of the restricted APIs relates to manipulating objects in a +dynamic manner, which is not compatible with static typing. The usage of +the following APIs is prohibited: Properties and functions of the global object: ``eval``, ``Infinity``, ``NaN``, ``isFinite``, ``isNaN``, ``parseFloat``, ``parseInt``, diff --git a/linter-4.2/docs/rules/recipe145.md b/linter-4.2/docs/rules/recipe145.md index 37e7be51f165a952926ccaf4efa3e2876798a817..92298aae7436b7fae77dfef1edbfd8c8c0d9419c 100644 --- a/linter-4.2/docs/rules/recipe145.md +++ b/linter-4.2/docs/rules/recipe145.md @@ -6,7 +6,7 @@ Rule ``arkts-strict-typing`` Type checker in ArkTS is not optional, the code must be explicitly and correctly types to be compiled and run. When porting from the standard TypeScript, -following flags should be turned on: ``noImplicitReturns``, +the following flags must be turned on: ``noImplicitReturns``, ``strictFunctionTypes``, ``strictNullChecks``, ``strictPropertyInitialization``. @@ -16,15 +16,21 @@ following flags should be turned on: ``noImplicitReturns``, ``` class C { - n: number - s: string + n: number // Compile-time error only with strictPropertyInitialization + s: string // Compile-time error only with strictPropertyInitialization } + // Compile-time error only with noImplicitReturns function foo(s: string): string { - console.log(s) + if (s != "") { + console.log(s) + return s + } else { + console.log(s) + } } - let n: number = null + let n: number = null // Compile-time error only with strictNullChecks ``` @@ -50,7 +56,7 @@ following flags should be turned on: ``noImplicitReturns``, ## See also -- Recipe 008: Use explicit types instead of ``any``, ``unknown`` (``arkts-no-any-undefined-unknown``) +- Recipe 008: Use explicit types instead of ``any``, ``unknown`` (``arkts-no-any-unknown``) - Recipe 146: Switching off type checks with in-place comments is not allowed (``arkts-strict-typing-required``) diff --git a/linter-4.2/docs/rules/recipe146.md b/linter-4.2/docs/rules/recipe146.md index f00a66b1563b5c3d4fac8f031a578988df342655..c47a215e5a90ee354ef430308f85e5d55c2f97d2 100644 --- a/linter-4.2/docs/rules/recipe146.md +++ b/linter-4.2/docs/rules/recipe146.md @@ -5,7 +5,7 @@ Rule ``arkts-strict-typing-required`` **Severity: error** Type checker in ArkTS is not optional, the code must be explicitly and -correctly types to be compiled and run. "Suppressing" type checker in-place +correctly typed to be compiled and run. "Suppressing" type checker in-place with special comments is not allowed. In particular, ``@ts-ignore`` and ``@ts-nocheck`` annotations are not supported. @@ -20,9 +20,10 @@ with special comments is not allowed. In particular, ``@ts-ignore`` and // Some code with switched off type checker // ... - // @ts-ignore let s1: string = null // No error, type checker suppressed - let s2: string = null // Compile-time error + + // @ts-ignore + let s2: string = null // No error, type checker suppressed ``` @@ -38,7 +39,7 @@ with special comments is not allowed. In particular, ``@ts-ignore`` and ## See also -- Recipe 008: Use explicit types instead of ``any``, ``unknown`` (``arkts-no-any-undefined-unknown``) +- Recipe 008: Use explicit types instead of ``any``, ``unknown`` (``arkts-no-any-unknown``) - Recipe 145: Strict type checking is enforced (``arkts-strict-typing``) diff --git a/linter-4.2/docs/rules/recipe147.md b/linter-4.2/docs/rules/recipe147.md index 6a81f41c8e315e4530e5fe285ca02d2cbbf24414..f158e98efda3b133a6746f1cae2e1c7db00f55da 100644 --- a/linter-4.2/docs/rules/recipe147.md +++ b/linter-4.2/docs/rules/recipe147.md @@ -4,8 +4,9 @@ Rule ``arkts-no-ts-deps`` **Severity: error** -Code base implemented in the standard TypeScript currently should not depend on ArkTS -through importing ArkTS code base. Imports in reverse direction are supported. +Currently, the code base implemented in the standard TypeScript language must not +depend on ArkTS through importing the |LANG| code base. Imports in reverse +direction are supported. ## TypeScript @@ -21,7 +22,23 @@ through importing ArkTS code base. Imports in reverse direction are supported. // lib.ts import { C } from "app" + +``` + +## ArkTS + + ``` + // lib1.ets + export class C { + // ... + } + + // lib2.ets + import { C } from "lib1" + + +``` diff --git a/linter-4.2/docs/rules/recipe148.md b/linter-4.2/docs/rules/recipe148.md new file mode 100644 index 0000000000000000000000000000000000000000..d17da6816175556e43d1faf47acfa49050367779 --- /dev/null +++ b/linter-4.2/docs/rules/recipe148.md @@ -0,0 +1,42 @@ +# No decorators except ArkUI decorators are currently allowed + +Rule ``arkts-no-decorators-except-arkui`` + +**Severity: error** + +Currently, only ArkUI decorators are allowed in the ArkTS. +Any other decorator will cause compile-time error. + + +## TypeScript + + +``` + + function classDecorator(x: any, y: any): void { + // + } + + @classDecorator + class BugReport { + } + + +``` + +## ArkTS + + +``` + + function classDecorator(x: any, y: any): void { + // + } + + @classDecorator // compile-time error: unsupported decorator + class BugReport { + } + +``` + + diff --git a/linter-4.2/docs/rules/recipe16.md b/linter-4.2/docs/rules/recipe16.md index 8fd0da8eef7975ee3ab9524cc6c193183d8e3de3..8e8037fd6c8e927b14c3dd2dc7f085d52795a453 100644 --- a/linter-4.2/docs/rules/recipe16.md +++ b/linter-4.2/docs/rules/recipe16.md @@ -4,8 +4,8 @@ Rule ``arkts-no-multiple-static-blocks`` **Severity: error** -ArkTS does not allow to have sevaral static block for class initialization, -combine static blocks statements to the one static block. +ArkTS does not allow having sevaral static blocks for class initialization. +Combine static block statements into one static block. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe17.md b/linter-4.2/docs/rules/recipe17.md index 4dd591c0eb9e6cf06a34a85862ae84f7057d2a83..93066f260be9ab7a3d18f5975b00278abd6fb723 100644 --- a/linter-4.2/docs/rules/recipe17.md +++ b/linter-4.2/docs/rules/recipe17.md @@ -32,7 +32,7 @@ ArkTS does not allow indexed signatures, use arrays instead. ``` class X { - public f: string[] + public f: string[] = [] } let myArray: X = new X() diff --git a/linter-4.2/docs/rules/recipe19.md b/linter-4.2/docs/rules/recipe19.md index 41baed871b8624b8478983a8e9c8b9b8a433b5d8..0d36274e57c08f8415890d019c9df9bb0ea5de9f 100644 --- a/linter-4.2/docs/rules/recipe19.md +++ b/linter-4.2/docs/rules/recipe19.md @@ -4,8 +4,8 @@ Rule ``arkts-no-intersection-types`` **Severity: error** -Currently, ArkTS does not support intersection types. You can use inheritance -as a work-around. +Currently, ArkTS does not support intersection types. Use inheritance +as a workaround. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe2.md b/linter-4.2/docs/rules/recipe2.md index 623cbff522305e062b19fe33f58679239e9afd37..04a96f96710df337f784ec458939864ec04f927b 100644 --- a/linter-4.2/docs/rules/recipe2.md +++ b/linter-4.2/docs/rules/recipe2.md @@ -29,7 +29,7 @@ and cannot be changed at runtime. ``` class SomeClass { - public someProperty : string + public someProperty : string = "" } let o = new SomeClass() diff --git a/linter-4.2/docs/rules/recipe22.md b/linter-4.2/docs/rules/recipe22.md index 1ea9a18cba4a8c3a8266404deeb4813963df4313..9637228b2ac7a9e89d906130804c204ae5b5e98e 100644 --- a/linter-4.2/docs/rules/recipe22.md +++ b/linter-4.2/docs/rules/recipe22.md @@ -5,8 +5,8 @@ Rule ``arkts-no-conditional-types`` **Severity: error** ArkTS does not support conditional type aliases. Introduce a new type with -constraints explicitly or rewrite logic with use of ``Object``. ``infer`` -keyword is not supported. +constraints explicitly, or rewrite logic using ``Object``. The keyword +``infer`` is not supported. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe24.md b/linter-4.2/docs/rules/recipe24.md deleted file mode 100644 index 174d840558bf5fa39a0a3acc06f8c5fb2455995c..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe24.md +++ /dev/null @@ -1,46 +0,0 @@ -# Optional parameters are not supported for primitive types - -Rule ``arkts-no-opt-params`` - -**Severity: error** - -ArkTS does not support optional parameters of primitive types. -You can use default parameter values or reference types. For reference types, -non-specified optional parameter is set to ``null``. - - -## TypeScript - - -``` - - // x is an optional parameter: - function f(x?: number) { - console.log(x) // log undefined or number - } - - // x is a required parameter with the default value: - function g(x: number = 1) { - console.log(x) - } - -``` - -## ArkTS - - -``` - - // You can use reference type (will be set to null if missing): - function f(x?: Number) { - console.log(x) // log null or some number - } - - // You can use a required parameter with the default value: - function g(x: number = 1) { - console.log(x) - } - -``` - - diff --git a/linter-4.2/docs/rules/recipe25.md b/linter-4.2/docs/rules/recipe25.md index 035716d5ba8f3746ce66da0dc78b093c5314cdce..8c611cd812e3eea5dd7c0fea02e3036ac58b7925 100644 --- a/linter-4.2/docs/rules/recipe25.md +++ b/linter-4.2/docs/rules/recipe25.md @@ -5,7 +5,7 @@ Rule ``arkts-no-ctor-prop-decls`` **Severity: error** ArkTS does not support declaring class fields in the ``constructor``. -You must declare them inside the ``class`` declaration instead. +Declare class fields inside the ``class`` declaration instead. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe27.md b/linter-4.2/docs/rules/recipe27.md index 776f84abd467964457189dea3d7fc5176c3cff92..79a3143089d5e14fe41414f653d96e28916e6028 100644 --- a/linter-4.2/docs/rules/recipe27.md +++ b/linter-4.2/docs/rules/recipe27.md @@ -1,4 +1,4 @@ -# Construct signatures not supported in interfaces +# Construct signatures are not supported in interfaces Rule ``arkts-no-ctor-signatures-iface`` diff --git a/linter-4.2/docs/rules/recipe3.md b/linter-4.2/docs/rules/recipe3.md index 3611a35c26e4a565c3d896bae9d7ca0fa7ccec3c..2c909880589d086eefad06796946d874eda2d47c 100644 --- a/linter-4.2/docs/rules/recipe3.md +++ b/linter-4.2/docs/rules/recipe3.md @@ -4,8 +4,8 @@ Rule ``arkts-no-private-identifiers`` **Severity: error** -ArkTS does not private identifiers started with ``#`` symbol, use ``private`` -keyword instead. +ArkTS does not use private identifiers starting with ``#`` symbol, use +the keyword ``private`` instead. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe30.md b/linter-4.2/docs/rules/recipe30.md index 78cd9278b8682f8285265d3031a65324fb417014..d0a069e36874e850fb5b3394d2ba3bbe4c516ff0 100644 --- a/linter-4.2/docs/rules/recipe30.md +++ b/linter-4.2/docs/rules/recipe30.md @@ -5,7 +5,7 @@ Rule ``arkts-no-structural-identity`` **Severity: error** Currently, ArkTS does not support structural identity, i.e., the compiler -cannot compare two types' public APIs and decide whether such types are +cannot compare public APIs of two types and decide whether such types are identical. Use other mechanisms (inheritance, interfaces or type aliases) instead. diff --git a/linter-4.2/docs/rules/recipe31.md b/linter-4.2/docs/rules/recipe31.md index bd8337a3c10d6777cb8de0336f455a571071136f..d2eef94fb799c2e19a428b8bce0963a3ab2279b0 100644 --- a/linter-4.2/docs/rules/recipe31.md +++ b/linter-4.2/docs/rules/recipe31.md @@ -5,7 +5,7 @@ Rule ``arkts-no-structural-subtyping`` **Severity: error** Currently, ArkTS does not check structural equivalence for type inference, -i.e., the compiler cannot compare two types' public APIs and decide whether +i.e., the compiler cannot compare public APIs of two types and decide whether such types are identical. Use other mechanisms (inheritance or interfaces) instead. diff --git a/linter-4.2/docs/rules/recipe32.md b/linter-4.2/docs/rules/recipe32.md index 6bb5251a6f4882f91abb699a758c5a775c50565e..cc7e416288e20cba4e968eb643567eb421aa1198 100644 --- a/linter-4.2/docs/rules/recipe32.md +++ b/linter-4.2/docs/rules/recipe32.md @@ -5,8 +5,8 @@ Rule ``arkts-no-structural-assignability`` **Severity: error** Currently, ArkTS does not check structural equivalence when checking if types -are assignable to each other, i.e., the compiler cannot compare two types' -public APIs and decide whether such types are identical. Use other mechanisms +are assignable to each other, i.e., the compiler cannot compare public APIs of +two types and decide whether such types are identical. Use other mechanisms (inheritance or interfaces) instead. diff --git a/linter-4.2/docs/rules/recipe33.md b/linter-4.2/docs/rules/recipe33.md deleted file mode 100644 index 4b8adbb0738364eac0d715f7718df1f6d3858cb4..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe33.md +++ /dev/null @@ -1,58 +0,0 @@ -# Optional properties are not supported for primitive types - -Rule ``arkts-no-opt-props`` - -**Severity: error** - -ArkTS does not support optional properties of primitive types. You can use -properties with default values or reference types. For reference types, -non-specified optional property is set to ``null``. This rule applies both to -classes and interfaces. - - -## TypeScript - - -``` - - class CompilerOptions { - strict?: boolean - sourcePath?: string - targetPath?: string - } - - let options: CompilerOptions = { - strict: true, - sourcePath: "./src" - } - - if (options.targetPath == undefined) { - // Some logic - } - -``` - -## ArkTS - - -``` - - class CompilerOptions { - strict: boolean = false - sourcePath: string = "" - targetPath?: string - } - - let options: CompilerOptions = { - strict: true, - sourcePath: "./src" - // targetPath is implicitly set to null - } - - if (options.targetPath == null) { - // Some logic - } - -``` - - diff --git a/linter-4.2/docs/rules/recipe34.md b/linter-4.2/docs/rules/recipe34.md index c1536ee24c555c21ca85b3406ef7b71f8e1b2a7c..3e6b617c33ea87634fd21c0f594791fe0753aba6 100644 --- a/linter-4.2/docs/rules/recipe34.md +++ b/linter-4.2/docs/rules/recipe34.md @@ -5,8 +5,8 @@ Rule ``arkts-no-inferred-generic-params`` **Severity: error** ArkTS allows to omit generic type parameters if it is possible to infer -the concrete types from the parameters passed to the function. Otherwise a -compile-time error occurs. In particular, inference of generic type parameters +the concrete types from the parameters passed to the function. A compile-time +error occurs otherwise. In particular, inference of generic type parameters based only on function return types is prohibited. diff --git a/linter-4.2/docs/rules/recipe35.md b/linter-4.2/docs/rules/recipe35.md index 0aab3e3bd5e4dd833239b624fb8665dc7857ab94..9645463e70b99ec7ca1991b3d4a29ccb3a5f024c 100644 --- a/linter-4.2/docs/rules/recipe35.md +++ b/linter-4.2/docs/rules/recipe35.md @@ -5,7 +5,7 @@ Rule ``arkts-no-structural-inference`` **Severity: error** Currently, ArkTS does not support structural typing, i.e., the compiler cannot -compare two types' public APIs and decide whether such types are identical. +compare public APIs of two types and decide whether such types are identical. Use inheritance and interfaces to specify the relation between the types explicitly. diff --git a/linter-4.2/docs/rules/recipe37.md b/linter-4.2/docs/rules/recipe37.md index f0ee0385f1834eedc06465068fa19348b5bb9fc4..862c026b284c1a936b8a2a458d13fdd096fb17a2 100644 --- a/linter-4.2/docs/rules/recipe37.md +++ b/linter-4.2/docs/rules/recipe37.md @@ -4,8 +4,8 @@ Rule ``arkts-no-regexp-literals`` **Severity: error** -Currently, ArkTS does not support RegExp literals. Use library call with string -literals instead. +Currently, ArkTS does not support RegExp literals. Use library call with +string literals instead. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe38.md b/linter-4.2/docs/rules/recipe38.md index 62b06f7a93706c30e1481c1c77d94e2a7bf8bc96..a0fdb0a06e953ac517d0fe5c18d2dfa4af205d95 100644 --- a/linter-4.2/docs/rules/recipe38.md +++ b/linter-4.2/docs/rules/recipe38.md @@ -5,9 +5,9 @@ Rule ``arkts-no-untyped-obj-literals`` **Severity: error** ArkTS supports usage of object literals if the compiler can infer to what -classes or interfaces such literals correspond to. Otherwise, a compile-time -error occurs. Specifically, using literals to initialize classes and interfaces -is not supported in following contexts: +classes or interfaces such literals correspond to. A compile-time error +occurs otherwise. Using literals to initialize classes and interfaces is +specifically not supported in the following contexts: * Initialization of anything that has ``any``, ``Object``, or ``object`` type * Initialization of classes or interfaces with methods diff --git a/linter-4.2/docs/rules/recipe40.md b/linter-4.2/docs/rules/recipe40.md index 9fd22580fbf472bf9bebc48b03bca25c45d5cced..dd4221b1457b466a4de5876eb63a5733c817c18d 100644 --- a/linter-4.2/docs/rules/recipe40.md +++ b/linter-4.2/docs/rules/recipe40.md @@ -28,8 +28,8 @@ types in place. Declare classes and interfaces explicitly instead. ``` class O { - x: number - y: number + x: number = 0 + y: number = 0 } let o: O = {x: 2, y: 3} diff --git a/linter-4.2/docs/rules/recipe43.md b/linter-4.2/docs/rules/recipe43.md index e7257c77bf3037ae9ee155289e77b27aedb93be2..b2ee2d200504ffbeb5be1e50dc1d50e5a9e72105 100644 --- a/linter-4.2/docs/rules/recipe43.md +++ b/linter-4.2/docs/rules/recipe43.md @@ -5,8 +5,8 @@ Rule ``arkts-no-noninferrable-arr-literals`` **Severity: error** Basically, ArkTS infers the type of an array literal as a union type of its -contents. But if there is at least one element with a non-inferrable type -(e.g. untyped object literal), a compile-time error occurs. +contents. However, a compile-time error occurs if there is at least one +element with a non-inferrable type (e.g. untyped object literal). ## TypeScript diff --git a/linter-4.2/docs/rules/recipe45.md b/linter-4.2/docs/rules/recipe45.md index 20680c932cca974c8ba607d0e640b81b7697af7b..70f14066e4d58cd71b81e1178978f4aa176cdbb5 100644 --- a/linter-4.2/docs/rules/recipe45.md +++ b/linter-4.2/docs/rules/recipe45.md @@ -13,7 +13,8 @@ to be explicitly specified. ``` - let f = (s) => { // type any is assumed + // Compile-time error only with noImplicitAny: + let f = (s /* type any is assumed */) => { console.log(s) } diff --git a/linter-4.2/docs/rules/recipe46.md b/linter-4.2/docs/rules/recipe46.md index 71fcbf8a1fd450b512787156f304591e859c1033..cf1ac5cf39c0aea81ba60899f5ebbba54fb579e7 100644 --- a/linter-4.2/docs/rules/recipe46.md +++ b/linter-4.2/docs/rules/recipe46.md @@ -4,8 +4,8 @@ Rule ``arkts-no-func-expressions`` **Severity: error** -ArkTS does not support function expressions, use arrow functions instead -to be explicitly specified. +ArkTS does not support function expressions. Use arrow functions instead +to specify explicitly. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe50.md b/linter-4.2/docs/rules/recipe50.md index 1f839a5e1ca0a8ae5e9058fef0cb94ae615ecee4..cd27385343366867c9b9d4e7596431cb4064bfde 100644 --- a/linter-4.2/docs/rules/recipe50.md +++ b/linter-4.2/docs/rules/recipe50.md @@ -4,8 +4,8 @@ Rule ``arkts-no-class-literals`` **Severity: error** -ArkTS does not support class literals. A new named class type must be -introduced explicitly. +ArkTS does not support class literals. Introduce new named class types +explicitly. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe51.md b/linter-4.2/docs/rules/recipe51.md index 9b6ddc1fd820b6ee7dab6b681e90068ae0c04bbf..700a2409c4924fa197aee56960e8de244a85f5d7 100644 --- a/linter-4.2/docs/rules/recipe51.md +++ b/linter-4.2/docs/rules/recipe51.md @@ -29,7 +29,7 @@ may be specified. ``` interface C { - foo() + foo(): void } class C1 implements C { diff --git a/linter-4.2/docs/rules/recipe52.md b/linter-4.2/docs/rules/recipe52.md index 91042ae7740cc4270ead455db209cd7b71fe9219..fd7e53ae860369531f1c6c4204d56820281f21eb 100644 --- a/linter-4.2/docs/rules/recipe52.md +++ b/linter-4.2/docs/rules/recipe52.md @@ -6,7 +6,7 @@ Rule ``arkts-no-undefined-prop-access`` ArkTS supports accessing only those class properties that are either declared in the class, or accessible via inheritance. Accessing any other properties is -prohibited and causes compile-time errors. Use proper types to check property +prohibited, and causes compile-time errors. Use proper types to check property existence during compilation. @@ -19,7 +19,7 @@ existence during compilation. let n = person["name"] let e = person["isEmployee"] - let s = person["office"] // undefined + let s = person["office"] // Compile-time error only with noImplicitAny ``` diff --git a/linter-4.2/docs/rules/recipe53.md b/linter-4.2/docs/rules/recipe53.md index 17c22a056818e171f8dee46ffb6123ba01f2ebd6..825fe0f5bef4081515144dc5411f33560920205a 100644 --- a/linter-4.2/docs/rules/recipe53.md +++ b/linter-4.2/docs/rules/recipe53.md @@ -4,10 +4,13 @@ Rule ``arkts-as-casts`` **Severity: error** -ArkTS supports ``as`` keyword as the only syntax for type casts. +ArkTS supports the keyword ``as`` as the only syntax for type casts. Incorrect cast causes a compile-time error or runtime ``ClassCastException``. ```` syntax for type casts is not supported. +Use the expression ``new ...`` instead of ``as`` if a **primitive** type +(e.g., a ``number`` or a ``boolean``) must be cast to the reference type. + ## TypeScript @@ -31,6 +34,14 @@ Incorrect cast causes a compile-time error or runtime ``ClassCastException``. let c3 = createShape() as Square console.log(c3.y) // undefined + // Important corner case for casting primitives to the boxed counterparts: + // The left operand is not properly boxed here in in runtime + // because "as" has no runtime effect in TypeScript + let e1 = (5.0 as Number) instanceof Number // false + + // Number object is created and instanceof works as expected: + let e2 = (new Number(5.0)) instanceof Number // true + ``` ## ArkTS @@ -51,6 +62,9 @@ Incorrect cast causes a compile-time error or runtime ``ClassCastException``. // ClassCastException during runtime is thrown: let c3 = createShape() as Square + // Number object is created and instanceof works as expected: + let e2 = (new Number(5.0)) instanceof Number // true + ``` diff --git a/linter-4.2/docs/rules/recipe55.md b/linter-4.2/docs/rules/recipe55.md index 2b47bb52508ab4a126c165b41a62693b86879c9b..42eb50236a2d128756bf4570625b9bd0bc6fd247 100644 --- a/linter-4.2/docs/rules/recipe55.md +++ b/linter-4.2/docs/rules/recipe55.md @@ -15,14 +15,25 @@ be done explicitly. ``` - let a = +5 // 5 as number - let b = +"5" // 5 as number - let c = -5 // -5 as number - let d = -"5" // -5 as number - let e = ~5 // -6 as number - let f = ~"5" // -6 as number + let a = +5 // 5 as number + let b = +"5" // 5 as number + let c = -5 // -5 as number + let d = -"5" // -5 as number + let e = ~5 // -6 as number + let f = ~"5" // -6 as number let g = +"string" // NaN as number + function returnTen(): string { + return "-10" + } + + function returnString(): string { + return "string" + } + + let x = +returnTen() // -10 as number + let y = +returnString() // NaN + ``` ## ArkTS @@ -30,19 +41,25 @@ be done explicitly. ``` - let a = +5 // 5 as number - let b = +"5" // Compile-time error - let c = -5 // -5 as number - let d = -"5" // Compile-time error - let e = ~5 // -6 as number - let f = ~"5" // Compile-time error + let a = +5 // 5 as number + let b = +"5" // Compile-time error + let c = -5 // -5 as number + let d = -"5" // Compile-time error + let e = ~5 // -6 as number + let f = ~"5" // Compile-time error let g = +"string" // Compile-time error -``` + function returnTen(): string { + return "-10" + } + + function returnString(): string { + return "string" + } -## See also + let x = +returnTen() // Compile-time error + let y = +returnString() // Compile-time error -- Recipe 061: Binary operators ``*``, ``/``, ``%``, ``-``, ``<<``, ``>>``, ``>>>``, ``&``, ``^`` and ``|`` work only on numeric types (``arkts-no-polymorphic-binops``) -- Recipe 063: Binary ``+`` operator supports implicit casts only for numbers and strings (``arkts-no-polymorphic-plus``) +``` diff --git a/linter-4.2/docs/rules/recipe56.md b/linter-4.2/docs/rules/recipe56.md deleted file mode 100644 index 8f1cb613ec44ee44206e51be05f75ca7d28b1a25..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe56.md +++ /dev/null @@ -1,54 +0,0 @@ -# Unary ``+`` cannot be used for casting to ``number`` - -Rule ``arkts-no-unary-plus-cast`` - -**Severity: error** - -ArkTS does not support casting from any type to a numeric type -by using the unary ``+`` operator, which can be applied only to -numeric types. - - -## TypeScript - - -``` - - function returnTen(): string { - return "-10" - } - - function returnString(): string { - return "string" - } - - let a = +returnTen() // -10 as number - let b = +returnString() // NaN - -``` - -## ArkTS - - -``` - - function returnTen(): string { - return "-10" - } - - function returnString(): string { - return "string" - } - - let a = +returnTen() // Compile-time error - let b = +returnString() // Compile-time error - -``` - -## See also - -- Recipe 055: Unary operators ``+``, ``-`` and ``~`` work only on numbers (``arkts-no-polymorphic-unops``) -- Recipe 061: Binary operators ``*``, ``/``, ``%``, ``-``, ``<<``, ``>>``, ``>>>``, ``&``, ``^`` and ``|`` work only on numeric types (``arkts-no-polymorphic-binops``) -- Recipe 063: Binary ``+`` operator supports implicit casts only for numbers and strings (``arkts-no-polymorphic-plus``) - - diff --git a/linter-4.2/docs/rules/recipe59.md b/linter-4.2/docs/rules/recipe59.md index d64602e17550c8857100b5de5897a415472d816e..d2c937e46ea7a8f1d18950868e5e5e4bc8b24070 100644 --- a/linter-4.2/docs/rules/recipe59.md +++ b/linter-4.2/docs/rules/recipe59.md @@ -32,8 +32,8 @@ changed at runtime. Thus the operation of deleting a property makes no sense. // and assign null to mark value absence: class Point { - x: number | null - y: number | null + x: number | null = 0 + y: number | null = 0 } let p = new Point() diff --git a/linter-4.2/docs/rules/recipe60.md b/linter-4.2/docs/rules/recipe60.md index 38b6ca476730f4d5c1513353841f055db04ad590..0df74f8a6620d9d8f093c237e383cb677c8f9266 100644 --- a/linter-4.2/docs/rules/recipe60.md +++ b/linter-4.2/docs/rules/recipe60.md @@ -4,8 +4,8 @@ Rule ``arkts-no-type-query`` **Severity: error** -ArkTS supports ``typeof`` operator only in the expression context. Specifying -type notations using ``typeof`` is not supported. +ArkTS supports ``typeof`` operator only in the expression context. Using +``typeof`` to specify type notations is not supported. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe61.md b/linter-4.2/docs/rules/recipe61.md deleted file mode 100644 index e5bdfd14abd026bf364762ae3044274ce249ec5a..0000000000000000000000000000000000000000 --- a/linter-4.2/docs/rules/recipe61.md +++ /dev/null @@ -1,71 +0,0 @@ -# Binary operators ``*``, ``/``, ``%``, ``-``, ``<<``, ``>>``, ``>>>``, ``&``, ``^`` and ``|`` work only on numeric types - -Rule ``arkts-no-polymorphic-binops`` - -**Severity: error** - -ArkTS allows applying binary operators ``*``, ``/``, ``%``, ``-``, ``<<``, -``>>``, ``>>>``, ``&``, ``^`` and ``|`` only to values of numeric types. -Implicit casts from other types to numeric types are prohibited and cause -compile-time errors. - - -## TypeScript - - -``` - - let a = (5 & 5) // 5 - let b = (5.5 & 5.5) // 5, not 5.5 - let c = (5 | 5) // 5 - let d = (5.5 | 5.5) // 5, not 5.5 - - enum Direction { - Up = -1, - Down - } - let e = Direction.Up >> 1 // -1 - let f = Direction.Up >>> 1 // 2147483647 - - let g = ("10" as any) << 1 // 20 - let h = ("str" as any) << 1 // 0 - - let i = 10 * 5 - let j = 10 / 5 - let k = 10 % 5 - let l = 10 - 5 - -``` - -## ArkTS - - -``` - - let a = (5 & 5) // 5 - let b = (5.5 & 5.5) // Compile-time error - let c = (5 | 5) // 5 - let d = (5.5 | 5.5) // Compile-time error - - enum Direction { - Up, - Down - } - - let e = Direction.Up >> 1 // 0 - let f = Direction.Up >>> 1 // 0 - - let i = 10 * 5 - let j = 10 / 5 - let k = 10 % 5 - let l = 10 - 5 - -``` - -## See also - -- Recipe 055: Unary operators ``+``, ``-`` and ``~`` work only on numbers (``arkts-no-polymorphic-unops``) -- Recipe 056: Unary ``+`` cannot be used for casting to ``number`` (``arkts-no-unary-plus-cast``) -- Recipe 063: Binary ``+`` operator supports implicit casts only for numbers and strings (``arkts-no-polymorphic-plus``) - - diff --git a/linter-4.2/docs/rules/recipe63.md b/linter-4.2/docs/rules/recipe63.md index 47b26ae720c5bd3eb66b955de31c11a1847fc844..dc35085bd5a82120d4508d6e12abe98f65e3cda1 100644 --- a/linter-4.2/docs/rules/recipe63.md +++ b/linter-4.2/docs/rules/recipe63.md @@ -1,11 +1,16 @@ -# Binary ``+`` operator supports implicit casts only for numbers and strings +# Binary ``+`` operator supports implicit casts only for numbers, enums and strings Rule ``arkts-no-polymorphic-plus`` **Severity: error** -ArkTS supports implicit casts for ``+`` only for strings and numbers. -Elsewhere, any form of an explicit cast to string is required. +If one of the operands of binary ``+`` operator is +of the string type (including enum string constant), the other operand +can be of any type, and its value is implicitly converted to string. +Otherwise, ArkTS supports implicit casts for ``+`` only for +numbers and numeric enum constants. +ArkTS as TypeScript does not support ``+`` operator for booleans. +Elsewhere, any form of an explicit cast is required. ## TypeScript @@ -25,6 +30,11 @@ Elsewhere, any form of an explicit cast to string is required. let g = (new Object()) + "string" // "[object Object]string" + let i = true + true // JS: 2, TS: compile-time error + let j = true + 2 // JS: 3, TS: compile-time error + let k = E.E1 + true // JS: 1, TS: compile-time error + + ``` ## ArkTS @@ -44,12 +54,17 @@ Elsewhere, any form of an explicit cast to string is required. let g = (new Object()).toString() + "string" + + let i = true + true // compile-time error + let j = true + 2 // compile-time error + let k = E.E1 + true // compile-time error + + ``` ## See also - Recipe 055: Unary operators ``+``, ``-`` and ``~`` work only on numbers (``arkts-no-polymorphic-unops``) -- Recipe 056: Unary ``+`` cannot be used for casting to ``number`` (``arkts-no-unary-plus-cast``) -- Recipe 061: Binary operators ``*``, ``/``, ``%``, ``-``, ``<<``, ``>>``, ``>>>``, ``&``, ``^`` and ``|`` work only on numeric types (``arkts-no-polymorphic-binops``) + diff --git a/linter-4.2/docs/rules/recipe65.md b/linter-4.2/docs/rules/recipe65.md index b72b699c33296ad215ccdf06bb8dbe0d775ec4ec..89e956fcb88b38e57c2e7194f596a405bbee78e2 100644 --- a/linter-4.2/docs/rules/recipe65.md +++ b/linter-4.2/docs/rules/recipe65.md @@ -16,16 +16,15 @@ a type. ``` - class X {} + class X { + // ... + } let a = (new X()) instanceof Object // true - let b = (new X()) instanceof X // true - // left operand is a type: - let c = X instanceof Object // true - let d = X instanceof X // false + let b = (new X()) instanceof X // true - // left operand is not of type any - let e = (5.0 as Number) instanceof Number // false + let c = X instanceof Object // true, left operand is a type + let d = X instanceof X // false, left operand is a type ``` @@ -34,16 +33,15 @@ a type. ``` - class X {} + class X { + // ... + } let a = (new X()) instanceof Object // true - let b = (new X()) instanceof X // true - // left operand is a type: - let c = X instanceof Object // Compile-time error - let d = X instanceof X // Compile-time error + let b = (new X()) instanceof X // true - // left operand may be of any reference type, like number - let e = (5.0 as Number) instanceof Number // true + let c = X instanceof Object // Compile-time error, left operand is a type + let d = X instanceof X // Compile-time error, left operand is a type ``` diff --git a/linter-4.2/docs/rules/recipe66.md b/linter-4.2/docs/rules/recipe66.md index 10476f234c5e3f9161ff50e15ffff354d473b17f..20bc3b6bd9e011c05438ba688de8099e89792a7e 100644 --- a/linter-4.2/docs/rules/recipe66.md +++ b/linter-4.2/docs/rules/recipe66.md @@ -6,7 +6,7 @@ Rule ``arkts-no-in`` ArkTS does not support the ``in`` operator. However, this operator makes little sense since the object layout is known at compile time and cannot -be modified at runtime. Use ``instanceof`` as a work-around if you still need +be modified at runtime. Use ``instanceof`` as a workaround if you still need to check whether certain class members exist. diff --git a/linter-4.2/docs/rules/recipe69.md b/linter-4.2/docs/rules/recipe69.md index 50d253b2ff54bfdc67412df656e6216b2917ef5f..80725b25c2840f4e39e3029f6947af2bfb57c3e8 100644 --- a/linter-4.2/docs/rules/recipe69.md +++ b/linter-4.2/docs/rules/recipe69.md @@ -34,11 +34,11 @@ using a temporary variable, where applicable) can be used for replacement. one = two two = tmp - let data: Number[] = [1,2,3,4] + let data: Number[] = [1, 2, 3, 4] let head = data[0] - let tail = new Number[data.length - 1] + let tail: Number[] = [] for (let i = 1; i < data.length; ++i) { - tail[i - 1] = data[i] + tail.push(data[i]) } ``` diff --git a/linter-4.2/docs/rules/recipe76.md b/linter-4.2/docs/rules/recipe76.md index beec4a751ae90f39ff182623ee23c641cfb74187..e620ace6b77c7c94236bfa59ecd6bfb7be722515 100644 --- a/linter-4.2/docs/rules/recipe76.md +++ b/linter-4.2/docs/rules/recipe76.md @@ -4,9 +4,9 @@ Rule ``arkts-no-implied-inference`` **Severity: error** -Currently, ArkTS does not support inference of implied types. Use explicit -type notation instead. Use ``Object[]`` if you need containers that hold -data of mixed types. +Currently, ArkTS does not support inference of implied types. +Use explicit type notation instead. +Use ``Object[]`` if you need containers that hold data of mixed types. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe79.md b/linter-4.2/docs/rules/recipe79.md index 56ac1ea4fe71ac3ff98de23ffd33c202c6ef7458..30bcb8dd4c8a44e5bfaad30220545a77b3fc5616 100644 --- a/linter-4.2/docs/rules/recipe79.md +++ b/linter-4.2/docs/rules/recipe79.md @@ -4,8 +4,8 @@ Rule ``arkts-no-types-in-catch`` **Severity: error** -In TypeScript catch clause variable type annotation must be ``any`` or ``unknown`` -if specified. As ArkTS does not support these types, a type annotation should +In TypeScript, catch clause variable type annotation must be ``any`` or ``unknown`` +if specified. As ArkTS does not support these types, a type annotation must be omitted. @@ -17,7 +17,9 @@ be omitted. try { // some code } - catch (a: unknown) {} + catch (a: unknown) { + // handle error + } ``` @@ -29,7 +31,9 @@ be omitted. try { // some code } - catch (a) {} + catch (a) { + // handle error + } ``` diff --git a/linter-4.2/docs/rules/recipe8.md b/linter-4.2/docs/rules/recipe8.md index fd3e9e1004eb6050b4aff873026d39400e7bfc5b..bd35822de63e67c7a615005839478f7620899398 100644 --- a/linter-4.2/docs/rules/recipe8.md +++ b/linter-4.2/docs/rules/recipe8.md @@ -1,11 +1,11 @@ # Use explicit types instead of ``any``, ``unknown`` -Rule ``arkts-no-any-undefined-unknown`` +Rule ``arkts-no-any-unknown`` **Severity: error** -ArkTS does not support ``any``, and ``unknown`` types. Please specify types -explicitly. +ArkTS does not support the types ``any`` and ``unknown``. Please specify +types explicitly. ## TypeScript diff --git a/linter-4.2/docs/rules/recipe80.md b/linter-4.2/docs/rules/recipe80.md index 190320e611acd4d67c3f626eafea4c41ee6948fe..5c2b4306113a9f14d02728794e0be4ef6f32a013 100644 --- a/linter-4.2/docs/rules/recipe80.md +++ b/linter-4.2/docs/rules/recipe80.md @@ -6,8 +6,9 @@ Rule ``arkts-no-for-in`` ArkTS does not support the iteration over object contents by the ``for .. in`` loop. For objects, iteration over properties at runtime is -considered redundant because object layout is known at compile time and cannot -change at runtime. For arrays, you can iterate with the regular ``for`` loop. +considered redundant because object layout is known at compile time, and +cannot change at runtime. For arrays, you can iterate with the regular +``for`` loop. ## TypeScript @@ -36,7 +37,7 @@ change at runtime. For arrays, you can iterate with the regular ``for`` loop. ## See also -- Recipe 081: Iterable interfaces are not supported (``arkts-noiterable``) +- Recipe 081: Iterable interfaces are not supported (``arkts-no-iterable``) - Recipe 082: ``for-of`` is supported only for arrays and strings (``arkts-for-of-str-arr``) diff --git a/linter-4.2/docs/rules/recipe81.md b/linter-4.2/docs/rules/recipe81.md index 226c410979941cdf890162e0423e4058784cb69f..541ab965a2185eedb0d557038ef5bd8373f7998b 100644 --- a/linter-4.2/docs/rules/recipe81.md +++ b/linter-4.2/docs/rules/recipe81.md @@ -1,6 +1,6 @@ # Iterable interfaces are not supported -Rule ``arkts-noiterable`` +Rule ``arkts-no-iterable`` **Severity: error** diff --git a/linter-4.2/docs/rules/recipe82.md b/linter-4.2/docs/rules/recipe82.md index e6c28d9b6e920d1273549292c5de2f583899af51..1c2efa50e21e51be5d99b61737908473949c14cc 100644 --- a/linter-4.2/docs/rules/recipe82.md +++ b/linter-4.2/docs/rules/recipe82.md @@ -36,6 +36,6 @@ but does not support the iteration of objects content. ## See also - Recipe 080: ``for .. in`` is not supported (``arkts-no-for-in``) -- Recipe 081: Iterable interfaces are not supported (``arkts-noiterable``) +- Recipe 081: Iterable interfaces are not supported (``arkts-no-iterable``) diff --git a/linter-4.2/docs/rules/recipe84.md b/linter-4.2/docs/rules/recipe84.md index b0e6a27522c8d9e020153f4f444db529091aedba..f5d6f4bcb920046a677e1428724f22467d091fbb 100644 --- a/linter-4.2/docs/rules/recipe84.md +++ b/linter-4.2/docs/rules/recipe84.md @@ -8,4 +8,26 @@ ArkTS does not support the ``with`` statement. Use other language idioms (including fully qualified names of functions) to achieve the same behaviour. +## TypeScript + + +``` + + with (Math) { // Compile-time error, but JavaScript code still emitted + let r: number = 42 + console.log("Area: ", PI * r * r) + } + +``` + +## ArkTS + + +``` + + let r: number = 42 + console.log("Area: ", Math.PI * r * r) + +``` + diff --git a/linter-4.2/docs/rules/recipe90.md b/linter-4.2/docs/rules/recipe90.md index 0613f6913d23d1eef21feed846cb1e9e10136829..2ef93392aa5625d59e0633641fabdb69460ef680 100644 --- a/linter-4.2/docs/rules/recipe90.md +++ b/linter-4.2/docs/rules/recipe90.md @@ -16,6 +16,7 @@ explicitly. ``` + // Compile-time error with noImplicitAny function f(x: number) { if (x <= 0) { return x @@ -23,6 +24,7 @@ explicitly. return g(x) } + // Compile-time error with noImplicitAny function g(x: number) { return f(x - 1) } diff --git a/linter-4.2/docs/rules/recipe93.md b/linter-4.2/docs/rules/recipe93.md index ceb0cacf5618c9c4847d9c33c0193ecda6c91a1d..ab4bce316abf01eaf8491b7d1c1d055c5ddd5527 100644 --- a/linter-4.2/docs/rules/recipe93.md +++ b/linter-4.2/docs/rules/recipe93.md @@ -14,7 +14,7 @@ ArkTS does not support the usage of ``this`` inside stand-alone functions. ``` function foo(i: number) { - this.count = i + this.count = i // Compile-time error only with noImplicitThis } class A { diff --git a/linter-4.2/docs/rules/recipe99.md b/linter-4.2/docs/rules/recipe99.md index 23349a239547a94390ff82ff5f94c484d00727a1..f0145e2cf7780932354e27b1f6e7faeca4a75a2c 100644 --- a/linter-4.2/docs/rules/recipe99.md +++ b/linter-4.2/docs/rules/recipe99.md @@ -5,7 +5,7 @@ Rule ``arkts-no-spread`` **Severity: error** The only supported scenario for the spread operator is to spread an array into -the rest parameter. Otherwise manually "unpack" data from arrays and objects, +the rest parameter. Otherwise, manually "unpack" data from arrays and objects, where necessary. diff --git a/linter-4.2/src/AutofixInfo.ts b/linter-4.2/src/AutofixInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0d25dbd25e6e8c99c812ba2d08bcb6f80ed6a06 --- /dev/null +++ b/linter-4.2/src/AutofixInfo.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface AutofixInfo { + problemID: string; + start: number; + end: number; +} \ No newline at end of file diff --git a/linter-4.2/src/Autofixer.ts b/linter-4.2/src/Autofixer.ts index fa10fa70dcc995be10897bd44a4c548207d51cae..1336103d5ab5a661501e0937efea784f0016c7bf 100644 --- a/linter-4.2/src/Autofixer.ts +++ b/linter-4.2/src/Autofixer.ts @@ -14,59 +14,33 @@ */ import * as ts from 'typescript'; -import { AutofixInfo } from './Common'; +import { AutofixInfo } from './AutofixInfo'; import { FaultID } from './Problems'; -import * as Utils from './Utils'; export const AUTOFIX_ALL: AutofixInfo = { problemID: '', start: -1, end: -1 } -export const autofixInfo: AutofixInfo[] = []; - -export function shouldAutofix(node: ts.Node, faultID: FaultID): boolean { - if (autofixInfo.length === 0) return false; - if (autofixInfo.length === 1 && autofixInfo[0] == AUTOFIX_ALL) return true; - return autofixInfo.findIndex( - value => value.start === node.getStart() && value.end === node.getEnd() && value.problemID === FaultID[faultID] - ) !== -1; -} - export interface Autofix { replacementText: string; start: number; end: number; } -const printer: ts.Printer = ts.createPrinter({ omitTrailingSemicolon: false, removeComments: false }); - -function numericLiteral2IdentifierName(numeric: ts.NumericLiteral) { - return '__' + numeric.getText(); -} - -function stringLiteral2IdentifierName(str: ts.StringLiteral) { - let text = (str as ts.StringLiteral).getText(); - return text.substring(1, text.length-1); // cut out starting and ending quoters. -} - -function propertyName2IdentifierName(name: ts.PropertyName): string { - if (name.kind === ts.SyntaxKind.NumericLiteral) - return numericLiteral2IdentifierName(name as ts.NumericLiteral); - - if (name.kind === ts.SyntaxKind.StringLiteral) - return stringLiteral2IdentifierName(name as ts.StringLiteral); - - return ''; -} +export class AutofixInfoSet { + private autofixInfo: AutofixInfo[]; -function indexExpr2IdentifierName(index: ts.Expression) { - if (index.kind === ts.SyntaxKind.NumericLiteral) - return numericLiteral2IdentifierName(index as ts.NumericLiteral); + constructor(autofixInfo: AutofixInfo[] | undefined) { + this.autofixInfo = autofixInfo ? autofixInfo : []; + } - if (index.kind === ts.SyntaxKind.StringLiteral) - return stringLiteral2IdentifierName(index as ts.StringLiteral); - - return ''; + public shouldAutofix(node: ts.Node, faultID: FaultID): boolean { + if (this.autofixInfo.length === 0) return false; + if (this.autofixInfo.length === 1 && this.autofixInfo[0] == AUTOFIX_ALL) return true; + return this.autofixInfo.findIndex( + value => value.start === node.getStart() && value.end === node.getEnd() && value.problemID === FaultID[faultID] + ) !== -1; + } } export function fixLiteralAsPropertyName(node: ts.Node): Autofix[] | undefined { @@ -76,7 +50,6 @@ export function fixLiteralAsPropertyName(node: ts.Node): Autofix[] | undefined { if (identName) return [{ replacementText: identName, start: propName.getStart(), end: propName.getEnd() }]; } - return undefined; } @@ -90,31 +63,9 @@ export function fixPropertyAccessByIndex(node: ts.Node): Autofix[] | undefined { start: elemAccess.getStart(), end: elemAccess.getEnd() }]; } - return undefined; } -export function fixBigIntLiteral(tsBigIntLiteral: ts.BigIntLiteral, isStringArg: boolean): Autofix[] { - let value = tsBigIntLiteral.getText(); - if (value.endsWith('n')) value = value.substring(0, value.length - 1); - - // Note: BigInt constructor can't parse literal value - // with underscore chars if ctor argument is a string. - let newNode = ts.factory.createCallExpression( - ts.factory.createIdentifier('BigInt'), - undefined, - [isStringArg - ? ts.factory.createStringLiteral(value.replace(/_/g, ''), true) - : ts.factory.createNumericLiteral(value)] - ); - - return [{ - replacementText: printer.printNode(ts.EmitHint.Unspecified, newNode, tsBigIntLiteral.getSourceFile()), - start: tsBigIntLiteral.getStart(), - end: tsBigIntLiteral.getEnd(), - }]; -} - export function fixParamWithoutType(param: ts.ParameterDeclaration, paramType: ts.TypeNode, isFuncExprParam: boolean = false): Autofix | ts.ParameterDeclaration { let paramWithType = ts.factory.createParameterDeclaration( @@ -144,6 +95,88 @@ export function fixReturnType(funcLikeDecl: ts.FunctionLikeDeclaration, typeNode return { start: pos, end: pos, replacementText: text }; } +export function dropTypeOnVarDecl(varDecl: ts.VariableDeclaration): Autofix { + let newVarDecl = ts.factory.createVariableDeclaration(varDecl.name, undefined, undefined, undefined); + let text = printer.printNode(ts.EmitHint.Unspecified, newVarDecl, varDecl.getSourceFile()); + return { start: varDecl.getStart(), end: varDecl.getEnd(), replacementText: text}; +} + +export function wrapExpressionInError(expr: ts.Expression, isString: boolean): Autofix { + let ctorArgs = isString ? [ expr ] : [ ts.factory.createStringLiteral('', true), expr]; + let newExpr = ts.factory.createNewExpression(ts.factory.createIdentifier('Error'), undefined, ctorArgs); + let text = printer.printNode(ts.EmitHint.Unspecified, newExpr, expr.getSourceFile()); + return { start: expr.getStart(), end: expr.getEnd(), replacementText: text }; +} + +export function dropTypeOnlyFlag( + impExpNode: ts.ImportClause | ts.ExportDeclaration +): Autofix { + let text: string; + if (ts.isImportClause(impExpNode)) { + let newImportClause = ts.factory.createImportClause(false, impExpNode.name, impExpNode.namedBindings); + text = printer.printNode(ts.EmitHint.Unspecified, newImportClause, impExpNode.getSourceFile()); + } + else { + let newExportDecl = ts.factory.createExportDeclaration(undefined, impExpNode.modifiers, false, + impExpNode.exportClause, impExpNode.moduleSpecifier); + text = printer.printNode(ts.EmitHint.Unspecified, newExportDecl, impExpNode.getSourceFile()); + } + return { start: impExpNode.getStart(), end: impExpNode.getEnd(), replacementText: text }; +} + +export function fixDefaultImport(importClause: ts.ImportClause, + defaultSpec: ts.ImportSpecifier, nonDefaultSpecs: ts.ImportSpecifier[]): Autofix { + let nameBindings = nonDefaultSpecs.length > 0 ? ts.factory.createNamedImports(nonDefaultSpecs) : undefined; + let newImportClause = ts.factory.createImportClause(importClause.isTypeOnly, defaultSpec.name, nameBindings); + let text = printer.printNode(ts.EmitHint.Unspecified, newImportClause, importClause.getSourceFile()); + return { start: importClause.getStart(), end: importClause.getEnd(), replacementText: text }; +} + +export function fixTypeAssertion(typeAssertion: ts.TypeAssertion): Autofix { + const asExpr = ts.factory.createAsExpression(typeAssertion.expression, typeAssertion.type); + let text = printer.printNode(ts.EmitHint.Unspecified, asExpr, typeAssertion.getSourceFile()); + return { start: typeAssertion.getStart(), end: typeAssertion.getEnd(), replacementText: text }; +} + +export function fixReadonlyArr(node: ts.Node): Autofix { + return { + start: node.getStart(), + end: node.getEnd(), + replacementText: node.getText().substring('readonly '.length), + }; +} + +const printer: ts.Printer = ts.createPrinter(); + +function numericLiteral2IdentifierName(numeric: ts.NumericLiteral) { + return '__' + numeric.getText(); +} + +function stringLiteral2IdentifierName(str: ts.StringLiteral) { + let text = (str as ts.StringLiteral).getText(); + return text.substring(1, text.length-1); // cut out starting and ending quoters. +} + +function propertyName2IdentifierName(name: ts.PropertyName): string { + if (name.kind === ts.SyntaxKind.NumericLiteral) + return numericLiteral2IdentifierName(name as ts.NumericLiteral); + + if (name.kind === ts.SyntaxKind.StringLiteral) + return stringLiteral2IdentifierName(name as ts.StringLiteral); + + return ''; +} + +function indexExpr2IdentifierName(index: ts.Expression) { + if (index.kind === ts.SyntaxKind.NumericLiteral) + return numericLiteral2IdentifierName(index as ts.NumericLiteral); + + if (index.kind === ts.SyntaxKind.StringLiteral) + return stringLiteral2IdentifierName(index as ts.StringLiteral); + + return ''; +} + function getReturnTypePosition(funcLikeDecl: ts.FunctionLikeDeclaration): number { if (funcLikeDecl.body) { // Find position of the first node or token that follows parameters. @@ -164,49 +197,3 @@ function getReturnTypePosition(funcLikeDecl: ts.FunctionLikeDeclaration): number // Shouldn't get here. return -1; } - -export function fixCtorParameterProperties(ctorDecl: ts.ConstructorDeclaration, paramTypes: ts.TypeNode[]): Autofix[] | undefined { - let fieldInitStmts: ts.Statement[] = []; - let newFieldPos = ctorDecl.getStart(); - let autofixes: Autofix[] = [{ start: newFieldPos, end: newFieldPos, replacementText: '' }]; - - for (let i = 0; i < ctorDecl.parameters.length; i++) { - const param = ctorDecl.parameters[i]; - - // Parameter property can not be a destructuring parameter. - if (!ts.isIdentifier(param.name)) continue; - - if (Utils.hasAccessModifier(param)) { - let propIdent = ts.factory.createIdentifier(param.name.text); - - let newFieldNode = ts.factory.createPropertyDeclaration( - undefined, param.modifiers, propIdent, undefined, paramTypes[i], undefined - ); - let newFieldText = printer.printNode(ts.EmitHint.Unspecified, newFieldNode, ctorDecl.getSourceFile()) + '\n'; - autofixes[0].replacementText += newFieldText; - - let newParamDecl = ts.factory.createParameterDeclaration( - undefined, undefined, undefined, param.name, param.questionToken, param.type, param.initializer - ); - let newParamText = printer.printNode(ts.EmitHint.Unspecified, newParamDecl, ctorDecl.getSourceFile()); - autofixes.push({ start: param.getStart(), end: param.getEnd(), replacementText: newParamText }); - - fieldInitStmts.push(ts.factory.createExpressionStatement(ts.factory.createAssignment( - ts.factory.createPropertyAccessExpression( - ts.factory.createThis(), - propIdent, - ), - propIdent - ))); - } - } - - // Note: Bodyless ctors can't have parameter properties. - if (ctorDecl.body) { - let newBody = ts.factory.createBlock(fieldInitStmts.concat(ctorDecl.body.statements), true); - let newBodyText = printer.printNode(ts.EmitHint.Unspecified, newBody, ctorDecl.getSourceFile()); - autofixes.push({ start: ctorDecl.body.getStart(), end: ctorDecl.body.getEnd(), replacementText: newBodyText }); - } - - return autofixes; -} \ No newline at end of file diff --git a/linter-4.2/src/Common.ts b/linter-4.2/src/CommandLineOptions.ts similarity index 76% rename from linter-4.2/src/Common.ts rename to linter-4.2/src/CommandLineOptions.ts index 399a7d11621abb7e706086e7708d96d11994c07a..0dc423300d6d8c1e741eadcd2cf5d502dc8cb50a 100644 --- a/linter-4.2/src/Common.ts +++ b/linter-4.2/src/CommandLineOptions.ts @@ -14,12 +14,7 @@ */ import * as ts from 'typescript'; - -export interface AutofixInfo { - problemID: string; - start: number; - end: number; -} +import { AutofixInfo } from './AutofixInfo'; export interface CommandLineOptions { strictMode?: boolean; @@ -29,11 +24,4 @@ export interface CommandLineOptions { parsedConfigFile?: ts.ParsedCommandLine; inputFiles: string[]; autofixInfo?: AutofixInfo[]; -} - -// common options interface, additional fields may be used by plugins -export interface LintOptions { - cmdOptions: CommandLineOptions; - tsProgram?: ts.Program; - [key: string]: any; -} +} \ No newline at end of file diff --git a/linter-4.2/src/CommandLineParser.ts b/linter-4.2/src/CommandLineParser.ts index b9d3d3f9822344a9016b6595e76d4953b2653636..2638181a727bd31112045aef02a7db4e5c9439fc 100644 --- a/linter-4.2/src/CommandLineParser.ts +++ b/linter-4.2/src/CommandLineParser.ts @@ -15,7 +15,7 @@ import Logger from '../utils/logger'; import { logTscDiagnostic, decodeAutofixInfo } from './Utils'; -import { AutofixInfo } from './Common'; +import { CommandLineOptions } from './CommandLineOptions'; import { AUTOFIX_ALL } from './Autofixer'; import { Command, Option } from 'commander'; import * as ts from 'typescript'; @@ -29,16 +29,6 @@ const JSON_EXT = '.json'; const logger = Logger.getLogger(); -export interface CommandLineOptions { - strictMode?: boolean; - ideMode?: boolean; - logTscErrors?: boolean; - warningsAsErrors: boolean; - parsedConfigFile?: ts.ParsedCommandLine; - inputFiles: string[]; - autofixInfo?: AutofixInfo[]; -} - let inputFiles: string[]; let responseFile = ''; function addSrcFile(value: string, dummy: string) { diff --git a/linter-4.2/src/CompilerWrapper.ts b/linter-4.2/src/CompilerWrapper.ts index 48283e284d2c8353111d1a3f38020fdd7ed1cfbb..080906125bc8022e4fb1119365ea076c85edecf3 100644 --- a/linter-4.2/src/CompilerWrapper.ts +++ b/linter-4.2/src/CompilerWrapper.ts @@ -15,14 +15,15 @@ import * as ts from 'typescript'; import { logTscDiagnostic } from './Utils'; -import { TypeScriptLinter, consoleLog } from './TypeScriptLinter'; -import { CommandLineOptions, LintOptions } from './Common'; +import { consoleLog } from './TypeScriptLinter'; +import { CommandLineOptions } from './CommandLineOptions'; +import { LintOptions } from './LintOptions'; -export function compile(options: LintOptions): ts.Program { - const createProgramOptions = formTscOptions(options.cmdOptions); +export function compile(options: LintOptions, extraOptions?: any): ts.Program { + const createProgramOptions = formTscOptions(options.cmdOptions, extraOptions); const program = ts.createProgram(createProgramOptions); // Log Tsc errors if needed - if (TypeScriptLinter.logTscErrors) { + if (options.cmdOptions.logTscErrors) { const diagnostics = ts.getPreEmitDiagnostics(program); logTscDiagnostic(diagnostics, consoleLog); diagnostics.forEach((diagnostic) => { @@ -38,7 +39,7 @@ export function compile(options: LintOptions): ts.Program { return program; } -function formTscOptions(cmdOptions: CommandLineOptions): ts.CreateProgramOptions { +function formTscOptions(cmdOptions: CommandLineOptions, extraOptions?: any): ts.CreateProgramOptions { if (cmdOptions.parsedConfigFile) { let options: ts.CreateProgramOptions = { rootNames: cmdOptions.parsedConfigFile.fileNames, @@ -47,6 +48,10 @@ function formTscOptions(cmdOptions: CommandLineOptions): ts.CreateProgramOptions configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(cmdOptions.parsedConfigFile), }; + if (extraOptions) { + options.options = Object.assign(options.options, extraOptions); + } + return options; } @@ -55,5 +60,9 @@ function formTscOptions(cmdOptions: CommandLineOptions): ts.CreateProgramOptions options: { target: ts.ScriptTarget.Latest, module: ts.ModuleKind.CommonJS, }, }; + if (extraOptions) { + options.options = Object.assign(options.options, extraOptions); + } + return options; } diff --git a/linter-4.2/src/CookBookMsg.ts b/linter-4.2/src/CookBookMsg.ts index 57e28c882afa5a3e0efa98ee3e4acb3ed57fbb53..cd0a4c9732b258c4c43fa4caa7991aa345abe324 100644 --- a/linter-4.2/src/CookBookMsg.ts +++ b/linter-4.2/src/CookBookMsg.ts @@ -16,7 +16,7 @@ export const cookBookMsg: string[] = []; export const cookBookTag: string[] = []; -for( let i = 0; i < 147; i++) { +for( let i = 0; i < 148; i++) { cookBookMsg[ i ] = ''; } @@ -27,9 +27,9 @@ cookBookTag[ 4 ] = 'Use unique names for types, namespaces, etc. (arkts-unique-n cookBookTag[ 5 ] = 'Use "let" instead of "var" (arkts-no-var)'; cookBookTag[ 6 ] = ''; cookBookTag[ 7 ] = ''; -cookBookTag[ 8 ] = 'Use explicit types instead of "any", "unknown" (arkts-no-any-undefined-unknown)'; +cookBookTag[ 8 ] = 'Use explicit types instead of "any", "unknown" (arkts-no-any-unknown)'; cookBookTag[ 9 ] = ''; -cookBookTag[ 10 ] = '"bigint" is not a builtin type, suffix "n" for numeric literals is not supported (arkts-no-n-suffix)'; +cookBookTag[ 10 ] = ''; cookBookTag[ 11 ] = ''; cookBookTag[ 12 ] = ''; cookBookTag[ 13 ] = 'Use "Object[]" instead of tuples (arkts-no-tuples)'; @@ -46,7 +46,7 @@ cookBookTag[ 23 ] = ''; cookBookTag[ 24 ] = ''; cookBookTag[ 25 ] = 'Declaring fields in "constructor" is not supported (arkts-no-ctor-prop-decls)'; cookBookTag[ 26 ] = ''; -cookBookTag[ 27 ] = 'Construct signatures not supported in interfaces (arkts-no-ctor-signatures-iface)'; +cookBookTag[ 27 ] = 'Construct signatures are not supported in interfaces (arkts-no-ctor-signatures-iface)'; cookBookTag[ 28 ] = 'Indexed access types are not supported (arkts-no-aliases-by-index)'; cookBookTag[ 29 ] = 'Indexed access is not supported for fields (arkts-no-props-by-index)'; cookBookTag[ 30 ] = 'Structural identity is not supported (arkts-no-structural-identity)'; @@ -75,14 +75,14 @@ cookBookTag[ 52 ] = 'Attempt to access an undefined property is a compile-time e cookBookTag[ 53 ] = 'Only "as T" syntax is supported for type casts (arkts-as-casts)'; cookBookTag[ 54 ] = 'JSX expressions are not supported (arkts-no-jsx)'; cookBookTag[ 55 ] = 'Unary operators "+", "-" and "~" work only on numbers (arkts-no-polymorphic-unops)'; -cookBookTag[ 56 ] = 'Unary "+" cannot be used for casting to "number" (arkts-no-unary-plus-cast)'; +cookBookTag[ 56 ] = ''; cookBookTag[ 57 ] = ''; cookBookTag[ 58 ] = ''; cookBookTag[ 59 ] = '"delete" operator is not supported (arkts-no-delete)'; cookBookTag[ 60 ] = '"typeof" operator is allowed only in expression contexts (arkts-no-type-query)'; -cookBookTag[ 61 ] = 'Binary operators "*", "/", "%", "-", "<<", ">>", ">>>", "&", "^" and "|" work only on numeric types (arkts-no-polymorphic-binops)'; +cookBookTag[ 61 ] = ''; cookBookTag[ 62 ] = ''; -cookBookTag[ 63 ] = 'Binary "+" operator supports implicit casts only for numbers and strings (arkts-no-polymorphic-plus)'; +cookBookTag[ 63 ] = 'Binary "+" operator supports implicit casts only for numbers, enums and strings (arkts-no-polymorphic-plus)'; cookBookTag[ 64 ] = ''; cookBookTag[ 65 ] = '"instanceof" operator is partially supported (arkts-instanceof-ref-types)'; cookBookTag[ 66 ] = '"in" operator is not supported (arkts-no-in)'; @@ -100,7 +100,7 @@ cookBookTag[ 77 ] = ''; cookBookTag[ 78 ] = ''; cookBookTag[ 79 ] = 'Type annotation in catch clause is not supported (arkts-no-types-in-catch)'; cookBookTag[ 80 ] = '"for .. in" is not supported (arkts-no-for-in)'; -cookBookTag[ 81 ] = 'Iterable interfaces are not supported (arkts-noiterable)'; +cookBookTag[ 81 ] = 'Iterable interfaces are not supported (arkts-no-iterable)'; cookBookTag[ 82 ] = '"for-of" is supported only for arrays and strings (arkts-for-of-str-arr)'; cookBookTag[ 83 ] = 'Mapped type expression is not supported (arkts-no-mapped-types)'; cookBookTag[ 84 ] = '"with" statement is not supported (arkts-no-with)'; @@ -121,7 +121,7 @@ cookBookTag[ 98 ] = ''; cookBookTag[ 99 ] = 'It is possible to spread only arrays into the rest parameter (arkts-no-spread)'; cookBookTag[ 100 ] = ''; cookBookTag[ 101 ] = ''; -cookBookTag[ 102 ] = 'Interface declarations (extends same property) (arkts-no-extend-same-property)'; +cookBookTag[ 102 ] = 'Interface declarations (extends same property) (arkts-no-extend-same-prop)'; cookBookTag[ 103 ] = 'Declaration merging is not supported (arkts-no-decl-merging)'; cookBookTag[ 104 ] = 'Interfaces cannot extend classes (arkts-extends-only-class)'; cookBookTag[ 105 ] = 'Property-based runtime type checks are not supported (arkts-no-prop-existence-check)'; @@ -134,7 +134,7 @@ cookBookTag[ 111 ] = 'Enumeration members can be initialized only with compile t cookBookTag[ 112 ] = ''; cookBookTag[ 113 ] = '"enum" declaration merging is not supported (arkts-no-enum-merging)'; cookBookTag[ 114 ] = 'Namespaces cannot be used as objects (arkts-no-ns-as-obj)'; -cookBookTag[ 115 ] = 'Scripts and modules (arkts-no-scripts)'; +cookBookTag[ 115 ] = ''; cookBookTag[ 116 ] = 'Non-declaration statements in namespaces are not supported (arkts-no-ns-statements)'; cookBookTag[ 117 ] = ''; cookBookTag[ 118 ] = 'Special import type declarations are not supported (arkts-no-special-imports)'; @@ -143,7 +143,7 @@ cookBookTag[ 120 ] = '"import default as ..." is not supported (arkts-no-import- cookBookTag[ 121 ] = '"require" and "import" assignment are not supported (arkts-no-require)'; cookBookTag[ 122 ] = ''; cookBookTag[ 123 ] = ''; -cookBookTag[ 124 ] = 'Export list declaration is not supported (arkts-no-export-list-decl)'; +cookBookTag[ 124 ] = ''; cookBookTag[ 125 ] = 'Re-exporting is supported with restrictions (arkts-limited-reexport)'; cookBookTag[ 126 ] = '"export = ..." assignment is not supported (arkts-no-export-assignment)'; cookBookTag[ 127 ] = 'Special "export type" declarations are not supported (arkts-no-special-exports)'; @@ -167,3 +167,4 @@ cookBookTag[ 144 ] = 'Usage of standard library is restricted (arkts-limited-std cookBookTag[ 145 ] = 'Strict type checking is enforced (arkts-strict-typing)'; cookBookTag[ 146 ] = 'Switching off type checks with in-place comments is not allowed (arkts-strict-typing-required)'; cookBookTag[ 147 ] = 'No dependencies on TypeScript code are currently allowed (arkts-no-ts-deps)'; +cookBookTag[ 148 ] = 'No decorators except ArkUI decorators are currently allowed (arkts-no-decorators-except-arkui)'; diff --git a/linter-4.2/test/interfaces_optional_props.ts b/linter-4.2/src/LintOptions.ts similarity index 63% rename from linter-4.2/test/interfaces_optional_props.ts rename to linter-4.2/src/LintOptions.ts index 3b718173b681a7ad56e17707a79c1514f198f814..a2d1184f59fbf5189cc5e18a23cafab7b9c7fd37 100644 --- a/linter-4.2/test/interfaces_optional_props.ts +++ b/linter-4.2/src/LintOptions.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,17 +13,12 @@ * limitations under the License. */ -interface CompilerOptions { - strict?: boolean; - sourcePath?: string; - targetPath?: string; -} - -const options: CompilerOptions = { - strict: true, - sourcePath: './src', -}; +import * as ts from 'typescript'; +import { CommandLineOptions } from './CommandLineOptions'; -console.log(options); - -if (options.targetPath == undefined) console.log("'targetPath' is not defined"); +// common options interface, additional fields may be used by plugins +export interface LintOptions { + cmdOptions: CommandLineOptions; + tsProgram?: ts.Program; + [key: string]: any; +} diff --git a/linter/test/interfaces_optional_props.ts b/linter-4.2/src/LintRunResult.ts similarity index 62% rename from linter/test/interfaces_optional_props.ts rename to linter-4.2/src/LintRunResult.ts index 3b718173b681a7ad56e17707a79c1514f198f814..565b7bf6dfb2d7a980f25519eecb08ede6d686f7 100644 --- a/linter/test/interfaces_optional_props.ts +++ b/linter-4.2/src/LintRunResult.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,17 +13,9 @@ * limitations under the License. */ -interface CompilerOptions { - strict?: boolean; - sourcePath?: string; - targetPath?: string; -} +import { ProblemInfo } from './ProblemInfo'; -const options: CompilerOptions = { - strict: true, - sourcePath: './src', -}; - -console.log(options); - -if (options.targetPath == undefined) console.log("'targetPath' is not defined"); +export interface LintRunResult { + errorNodes: number; + problemsInfos: Map; +} \ No newline at end of file diff --git a/linter-4.2/src/LinterRunner.ts b/linter-4.2/src/LinterRunner.ts index 9c254fa276ad7a8f9e89d15970c79d9a2bf9f7a2..8c2f8d9038a4d3c8c9fbf98c45df2d37353a1270 100644 --- a/linter-4.2/src/LinterRunner.ts +++ b/linter-4.2/src/LinterRunner.ts @@ -14,35 +14,34 @@ */ import * as ts from 'typescript'; +import { ProblemInfo } from './ProblemInfo'; import { TypeScriptLinter, consoleLog } from './TypeScriptLinter'; import { FaultID, faultsAttrs } from './Problems'; import { parseCommandLine } from './CommandLineParser'; import { LinterConfig } from './TypeScriptLinterConfig'; +import { LintRunResult } from './LintRunResult'; import Logger from '../utils/logger'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as readline from 'node:readline'; import * as path from 'node:path'; import { compile } from './CompilerWrapper'; -import { CommandLineOptions, LintOptions } from './Common'; -import { autofixInfo } from './Autofixer'; +import { CommandLineOptions } from './CommandLineOptions'; +import { LintOptions } from './LintOptions'; +import { AutofixInfoSet } from './Autofixer'; +import { TSCCompiledProgram, getStrictOptions, transformDiagnostic } from './ts-diagnostics/TSCCompiledProgram'; +import { mergeArrayMaps } from './Utils'; const logger = Logger.getLogger(); -// Use static init methods, as TypeScript 4.2 doesn't support static blocks. -TypeScriptLinter.initStatic(); +// Use static init method for Linter configuration, as TypeScript 4.2 doesn't support static blocks. LinterConfig.initStatic(); -export function lint(options: LintOptions): number { +export function lint(options: LintOptions): LintRunResult { const cmdOptions = options.cmdOptions; - TypeScriptLinter.strictMode = !!cmdOptions.strictMode; - TypeScriptLinter.logTscErrors = !!cmdOptions.logTscErrors; - TypeScriptLinter.warningsAsErrors = cmdOptions.warningsAsErrors; - autofixInfo.length = 0; - if (cmdOptions.autofixInfo) autofixInfo.push(...cmdOptions.autofixInfo); - - const tsProgram = options.tsProgram ?? compile(options); + const tscDiagnosticsLinter = createLinter(options); + const tsProgram = tscDiagnosticsLinter.getOriginalProgram(); // Prepare list of input files for linter and retrieve AST for those files. let inputFiles: string[] = cmdOptions.inputFiles; @@ -63,61 +62,119 @@ export function lint(options: LintOptions): number { if (srcFile) srcFiles.push(srcFile); } - let problemFileCounter = lintFiles(srcFiles, tsProgram); + const tscStrictDiagnostics = getTscDiagnostics(tscDiagnosticsLinter, srcFiles); + + const linter = new TypeScriptLinter( + tsProgram.getTypeChecker(), + new AutofixInfoSet(cmdOptions.autofixInfo), + !!cmdOptions.strictMode, + cmdOptions.warningsAsErrors, + tscStrictDiagnostics + ); + const { errorNodes, problemsInfos } = lintFiles(srcFiles, linter); consoleLog('\n\n\nFiles scanned: ', srcFiles.length); - consoleLog('\nFiles with problems: ', problemFileCounter); + consoleLog('\nFiles with problems: ', errorNodes); - let errorNodes = 0, warningNodes = 0; + let errorNodesTotal = 0, warningNodes = 0; for (let i = 0; i < FaultID.LAST_ID; i++) { // if Strict mode - count all cases - if (!TypeScriptLinter.strictMode && faultsAttrs[i].migratable) // In relax mode skip migratable + if (!linter.strictMode && faultsAttrs[i].migratable) // In relax mode skip migratable continue; - if (faultsAttrs[i].warning) warningNodes += TypeScriptLinter.nodeCounters[i]; - else errorNodes += TypeScriptLinter.nodeCounters[i]; + if (faultsAttrs[i].warning) warningNodes += linter.nodeCounters[i]; + else errorNodesTotal += linter.nodeCounters[i]; } - logTotalProblemsInfo(errorNodes, warningNodes); - logProblemsPercentageByFeatures(); - return errorNodes; + logTotalProblemsInfo(errorNodesTotal, warningNodes, linter); + logProblemsPercentageByFeatures(linter); + return { + errorNodes: errorNodesTotal, + problemsInfos: mergeArrayMaps(problemsInfos, transformTscDiagnostics(tscStrictDiagnostics)), + }; } -function lintFiles(srcFiles: ts.SourceFile[], tsProgram: ts.Program) { +export function createLinter(options: LintOptions): TSCCompiledProgram { + if (options.tscDiagnosticsLinter) { + return options.tscDiagnosticsLinter; + } + const tsProgram = options.tsProgram ?? compile(options, getStrictOptions()); + return new TSCCompiledProgram(tsProgram, options); +} + +function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter): LintRunResult { let problemFiles = 0; + let problemsInfos: Map = new Map(); + for (const srcFile of srcFiles) { - const prevVisitedNodes = TypeScriptLinter.totalVisitedNodes; - const prevErrorLines = TypeScriptLinter.totalErrorLines; - const prevWarningLines = TypeScriptLinter.totalWarningLines; - TypeScriptLinter.errorLineNumbersString = ''; - TypeScriptLinter.warningLineNumbersString = ''; + const prevVisitedNodes = linter.totalVisitedNodes; + const prevErrorLines = linter.totalErrorLines; + const prevWarningLines = linter.totalWarningLines; + linter.errorLineNumbersString = ''; + linter.warningLineNumbersString = ''; const nodeCounters: number[] = []; for (let i = 0; i < FaultID.LAST_ID; i++) - nodeCounters[i] = TypeScriptLinter.nodeCounters[i]; + nodeCounters[i] = linter.nodeCounters[i]; - const linter = new TypeScriptLinter(srcFile, tsProgram); - linter.lint(); + linter.lint(srcFile); + // save results and clear problems array + problemsInfos.set( path.normalize(srcFile.fileName), [...linter.problemsInfos]); + linter.problemsInfos.length = 0; // print results for current file - const fileVisitedNodes = TypeScriptLinter.totalVisitedNodes - prevVisitedNodes; - const fileErrorLines = TypeScriptLinter.totalErrorLines - prevErrorLines; - const fileWarningLines = TypeScriptLinter.totalWarningLines - prevWarningLines; + const fileVisitedNodes = linter.totalVisitedNodes - prevVisitedNodes; + const fileErrorLines = linter.totalErrorLines - prevErrorLines; + const fileWarningLines = linter.totalWarningLines - prevWarningLines; problemFiles = countProblemFiles( - nodeCounters, problemFiles, srcFile, fileVisitedNodes, fileErrorLines, fileWarningLines + nodeCounters, problemFiles, srcFile, fileVisitedNodes, fileErrorLines, fileWarningLines, linter ); } - return problemFiles; + return { + errorNodes: problemFiles, + problemsInfos: problemsInfos, + }; +} + +/** + * Extracts TSC diagnostics emitted by strict checks. + * Function might be time-consuming, as it runs second compilation. + * @param sourceFiles AST of the processed files + * @param tscDiagnosticsLinter linter initialized with the processed program + * @returns problems found by TSC, mapped by `ts.SourceFile.fileName` field + */ +function getTscDiagnostics( + tscDiagnosticsLinter: TSCCompiledProgram, + sourceFiles: ts.SourceFile[], +): Map { + const strictDiagnostics = new Map(); + sourceFiles.forEach(file => { + const diagnostics = tscDiagnosticsLinter.getStrictDiagnostics(file); + if (diagnostics.length != 0) { + strictDiagnostics.set(path.normalize(file.fileName), diagnostics); + } + }); + return strictDiagnostics; +} + +function transformTscDiagnostics( + strictDiagnostics: Map +): Map { + const problemsInfos = new Map(); + strictDiagnostics.forEach((diagnostics, file, map) => { + problemsInfos.set(file, diagnostics.map(x => transformDiagnostic(x))); + }); + return problemsInfos; } function countProblemFiles( nodeCounters: number[], filesNumber: number, tsSrcFile: ts.SourceFile, - fileNodes: number, fileErrorLines: number, fileWarningLines: number + fileNodes: number, fileErrorLines: number, fileWarningLines: number, linter: TypeScriptLinter, ) { let errorNodes = 0, warningNodes = 0; for (let i = 0; i < FaultID.LAST_ID; i++) { - let nodeCounterDiff = TypeScriptLinter.nodeCounters[i] - nodeCounters[i]; + let nodeCounterDiff = linter.nodeCounters[i] - nodeCounters[i]; if (faultsAttrs[i].warning) warningNodes += nodeCounterDiff; else errorNodes += nodeCounterDiff; } @@ -126,8 +183,8 @@ function countProblemFiles( filesNumber++; let errorRate = ((errorNodes / fileNodes) * 100).toFixed(2); let warningRate = ((warningNodes / fileNodes) * 100).toFixed(2); - consoleLog(tsSrcFile.fileName, ': ', '\n\tError lines: ', TypeScriptLinter.errorLineNumbersString); - consoleLog(tsSrcFile.fileName, ': ', '\n\tWarning lines: ', TypeScriptLinter.warningLineNumbersString); + consoleLog(tsSrcFile.fileName, ': ', '\n\tError lines: ', linter.errorLineNumbersString); + consoleLog(tsSrcFile.fileName, ': ', '\n\tWarning lines: ', linter.warningLineNumbersString); consoleLog('\n\tError constructs (%): ', errorRate, '\t[ of ', fileNodes, ' constructs ], \t', fileErrorLines, ' lines'); consoleLog('\n\tWarning constructs (%): ', warningRate, '\t[ of ', fileNodes, ' constructs ], \t', fileWarningLines, ' lines'); } @@ -135,25 +192,25 @@ function countProblemFiles( return filesNumber; } -function logTotalProblemsInfo(errorNodes: number, warningNodes: number) { - let errorRate = ((errorNodes / TypeScriptLinter.totalVisitedNodes) * 100).toFixed(2); - let warningRate = ((warningNodes / TypeScriptLinter.totalVisitedNodes) * 100).toFixed(2); +function logTotalProblemsInfo(errorNodes: number, warningNodes: number, linter: TypeScriptLinter) { + let errorRate = ((errorNodes / linter.totalVisitedNodes) * 100).toFixed(2); + let warningRate = ((warningNodes / linter.totalVisitedNodes) * 100).toFixed(2); consoleLog('\nTotal error constructs (%): ', errorRate); consoleLog('\nTotal warning constructs (%): ', warningRate); - consoleLog('\nTotal error lines:', TypeScriptLinter.totalErrorLines, ' lines\n'); - consoleLog('\nTotal warning lines:', TypeScriptLinter.totalWarningLines, ' lines\n'); + consoleLog('\nTotal error lines:', linter.totalErrorLines, ' lines\n'); + consoleLog('\nTotal warning lines:', linter.totalWarningLines, ' lines\n'); } -function logProblemsPercentageByFeatures() { +function logProblemsPercentageByFeatures(linter: TypeScriptLinter) { consoleLog('\nPercent by features: '); for (let i = 0; i < FaultID.LAST_ID; i++) { // if Strict mode - count all cases - if (!TypeScriptLinter.strictMode && faultsAttrs[i].migratable) + if (!linter.strictMode && faultsAttrs[i].migratable) continue; - let nodes = TypeScriptLinter.nodeCounters[i]; - let lines = TypeScriptLinter.lineCounters[i]; - let pecentage = ((nodes / TypeScriptLinter.totalVisitedNodes) * 100).toFixed(2).padEnd(7, ' '); + let nodes = linter.nodeCounters[i]; + let lines = linter.lineCounters[i]; + let pecentage = ((nodes / linter.totalVisitedNodes) * 100).toFixed(2).padEnd(7, ' '); consoleLog(LinterConfig.nodeDesc[i].padEnd(55, ' '), pecentage, '[', nodes, ' constructs / ', lines, ' lines]'); } @@ -170,7 +227,7 @@ export function run() { if (!cmdOptions.ideMode) { const result = lint({ cmdOptions: cmdOptions }); - process.exit(result > 0 ? 1 : 0); + process.exit(result.errorNodes > 0 ? 1 : 0); } else { runIDEMode(cmdOptions); } @@ -199,20 +256,25 @@ function runIDEMode(cmdOptions: CommandLineOptions) { if (cmdOptions.parsedConfigFile) { cmdOptions.parsedConfigFile.fileNames.push(tmpFileName); } - lint({ cmdOptions: cmdOptions }); - const jsonMessage = TypeScriptLinter.problemsInfos.map((x) => ({ - line: x.line, - column: x.column, - start: x.start, - end: x.end, - type: x.type, - suggest: x.suggest, - rule: x.rule, - severity: x.severity, - autofixable: x.autofixable, - autofix: x.autofix - })); - logger.info('{"linter messages":' + JSON.stringify(jsonMessage) + '}'); + const result = lint({ cmdOptions: cmdOptions }); + const problems = Array.from(result.problemsInfos.values()); + if (problems.length === 1) { + const jsonMessage = problems[0].map((x) => ({ + line: x.line, + column: x.column, + start: x.start, + end: x.end, + type: x.type, + suggest: x.suggest, + rule: x.rule, + severity: x.severity, + autofixable: x.autofixable, + autofix: x.autofix + })); + logger.info(`{"linter messages":${JSON.stringify(jsonMessage)}}`); + } else { + logger.error('Unexpected error: could not lint file'); + } fs.unlinkSync(tmpFileName); }); } diff --git a/linter/test/func_def_params.ts b/linter-4.2/src/ProblemInfo.ts similarity index 66% rename from linter/test/func_def_params.ts rename to linter-4.2/src/ProblemInfo.ts index 20a310eed403a3b832be30ee4d68e78a6fa4e03f..0339677419324bf539b2ee156f874fc44e3264b3 100644 --- a/linter/test/func_def_params.ts +++ b/linter-4.2/src/ProblemInfo.ts @@ -13,18 +13,20 @@ * limitations under the License. */ -function foo(arg?: number) { - return arg; -} - -function bar(x?: string, y?: number, z?: boolean) { - return 3; -} - -function baz(a?: Number, b?: Boolean, c?: Object) { - return 3; -} +import { Autofix } from './Autofixer'; -function ff(a: number, s: string, x: boolean) { - return s; +export interface ProblemInfo { + line: number; + column: number; + endColumn: number; + start: number; + end: number; + type: string; + severity: number; + problem: string; + suggest: string; + rule: string; + ruleTag: number; + autofixable: boolean; + autofix?: Autofix[]; } diff --git a/linter-4.2/src/ProblemSeverity.ts b/linter-4.2/src/ProblemSeverity.ts new file mode 100644 index 0000000000000000000000000000000000000000..3736c692ef46726f5ecced3be6290c18c371f039 --- /dev/null +++ b/linter-4.2/src/ProblemSeverity.ts @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum ProblemSeverity { WARNING = 1, ERROR = 2 } diff --git a/linter-4.2/src/Problems.ts b/linter-4.2/src/Problems.ts index c001b4178e3a43152d0ade022a95353c691893d3..49696cf4d6ea7a5ea5bf06219ef543a6c1050222 100644 --- a/linter-4.2/src/Problems.ts +++ b/linter-4.2/src/Problems.ts @@ -14,26 +14,26 @@ */ export enum FaultID { - AnyType, UndefinedValue, SymbolType, TupleType, ObjectLiteralNoContextType, ArrayLiteralNoContextType, + AnyType, SymbolType, TupleType, ObjectLiteralNoContextType, ArrayLiteralNoContextType, ComputedPropertyName, LiteralAsPropertyName, TypeQuery, TupleLiteral, RegexLiteral, IsOperator, DestructuringParameter, YieldExpression, InterfaceOrEnumMerging, InterfaceExtendsClass, IndexMember, WithStatement, ThrowStatement, IndexedAccessType, UnknownType, ForInStatement, InOperator, KeyOfOperator, ImportFromPath, FunctionExpression, IntersectionType, - ObjectTypeLiteral, AddWithWrongType, BitOpWithWrongType, CommaOperator, LimitedReturnTypeInference, + ObjectTypeLiteral, AddWithWrongType, CommaOperator, LimitedReturnTypeInference, ArrowFunctionWithOmittedTypes, LambdaWithTypeParameters, ClassExpression, DestructuringAssignment, DestructuringDeclaration, ForOfNonArray, VarDeclaration, CatchWithUnsupportedType, DeleteOperator, - DeclWithDuplicateName, FuncOptionalParams, UnaryArithmNotNumber, ConstructorType, CallSignature, + DeclWithDuplicateName, UnaryArithmNotNumber, ConstructorType, CallSignature, TypeAssertion, PrivateIdentifier, LocalFunction, SwitchSelectorInvalidType, CaseExpressionNonConst, ConditionalType, MappedType, NamespaceAsObject, NonDeclarationInNamespace, GeneratorFunction, FunctionContainsThis, PropertyAccessByIndex, JsxElement, EnumMemberNonConstInit, ImplementsClass, MultipleStaticBlocks, ThisType, InferType, IntefaceExtendDifProps, StructuralIdentity, TypeOnlyImport, TypeOnlyExport, DefaultImport, - ExportRenaming, ExportListDeclaration, ReExporting, ExportAssignment, ImportAssignment, PropertyRuntimeCheck, - GenericCallNoTypeArgs, InterfaceOptionalProp, ParameterProperties, + LimitedReExporting, ExportAssignment, ImportAssignment, PropertyRuntimeCheck, + GenericCallNoTypeArgs, ParameterProperties, InstanceofUnsupported, ShorthandAmbientModuleDecl, WildcardsInModuleName, UMDModuleDefinition, JSExtensionInModuleIdent, NewTarget, DynamicImport, DefiniteAssignment, IifeAsNamespace, Prototype, GlobalThis, UtilityType, PropertyDeclOnFunction, FunctionApplyBindCall, ReadonlyArr, ConstAssertion, ImportAssertion, - BigIntLiteral, SpreadOperator, LimitedStdLibApi, + SpreadOperator, LimitedStdLibApi, ErrorSuppression, StrictDiagnostic, UnsupportedDecorators, LAST_ID, // this should always be last enum` } @@ -52,9 +52,7 @@ faultsAttrs[FaultID.PrivateIdentifier] = {migratable: true, cookBookRef: '3',}; faultsAttrs[FaultID.DeclWithDuplicateName] = {migratable: true, cookBookRef: '4',}; faultsAttrs[FaultID.VarDeclaration] = {migratable: true, cookBookRef: '5',}; faultsAttrs[FaultID.AnyType] = {cookBookRef: '8'}; -faultsAttrs[FaultID.UndefinedValue] = {cookBookRef: '8'}; faultsAttrs[FaultID.UnknownType] = {cookBookRef: '8',}; -faultsAttrs[FaultID.BigIntLiteral] = {migratable: true, cookBookRef: '10',}; faultsAttrs[FaultID.TupleType] = {cookBookRef: '13',}; faultsAttrs[FaultID.TupleLiteral] = {cookBookRef: '13',}; faultsAttrs[FaultID.CallSignature] = {cookBookRef: '14',}; @@ -64,12 +62,10 @@ faultsAttrs[FaultID.IndexMember] = {cookBookRef: '17',}; faultsAttrs[FaultID.IntersectionType] = {cookBookRef: '19',}; faultsAttrs[FaultID.ThisType] = {cookBookRef: '21',}; faultsAttrs[FaultID.ConditionalType] = {cookBookRef: '22',}; -faultsAttrs[FaultID.FuncOptionalParams] = {migratable: true, cookBookRef: '24',}; faultsAttrs[FaultID.ParameterProperties] = {migratable: true, cookBookRef: '25',}; faultsAttrs[FaultID.IndexedAccessType] = {cookBookRef: '28',}; faultsAttrs[FaultID.PropertyAccessByIndex] = {migratable: true, cookBookRef: '29',}; faultsAttrs[FaultID.StructuralIdentity] = {cookBookRef: '30',}; -faultsAttrs[FaultID.InterfaceOptionalProp] = {cookBookRef: '33',}; faultsAttrs[FaultID.GenericCallNoTypeArgs] = {cookBookRef: '34',}; faultsAttrs[FaultID.RegexLiteral] = {cookBookRef: '37',}; faultsAttrs[FaultID.ObjectLiteralNoContextType] = {cookBookRef: '38',}; @@ -85,7 +81,7 @@ faultsAttrs[FaultID.JsxElement] = {cookBookRef: '54',}; faultsAttrs[FaultID.UnaryArithmNotNumber] = {cookBookRef: '55',}; faultsAttrs[FaultID.DeleteOperator] = {cookBookRef: '59',}; faultsAttrs[FaultID.TypeQuery] = {cookBookRef: '60',}; -faultsAttrs[FaultID.BitOpWithWrongType] = {cookBookRef: '61',}; +// remove as rule#61: FaultID.BitOpWithWrongType => {cookBookRef: '61',}; faultsAttrs[FaultID.AddWithWrongType] = {cookBookRef: '63',}; faultsAttrs[FaultID.InstanceofUnsupported] = {cookBookRef: '65',}; faultsAttrs[FaultID.InOperator] = {cookBookRef: '66',}; @@ -121,9 +117,7 @@ faultsAttrs[FaultID.ImportFromPath] = {cookBookRef: '119',}; faultsAttrs[FaultID.TypeOnlyImport] = {migratable: true, cookBookRef: '118',}; faultsAttrs[FaultID.DefaultImport] = {migratable: true, cookBookRef: '120',}; faultsAttrs[FaultID.ImportAssignment] = {cookBookRef: '121',}; -faultsAttrs[FaultID.ExportRenaming] = {migratable: true, cookBookRef: '123',}; -faultsAttrs[FaultID.ExportListDeclaration] = {migratable: true, cookBookRef: '124',}; -faultsAttrs[FaultID.ReExporting] = {cookBookRef: '125',}; +faultsAttrs[FaultID.LimitedReExporting] = {cookBookRef: '125',}; faultsAttrs[FaultID.ExportAssignment] = {cookBookRef: '126',}; faultsAttrs[FaultID.TypeOnlyExport] = {migratable: true, cookBookRef: '127',}; faultsAttrs[FaultID.ShorthandAmbientModuleDecl] = {cookBookRef: '128',}; @@ -143,3 +137,6 @@ faultsAttrs[FaultID.ReadonlyArr] = {migratable: true, cookBookRef: '141',}; faultsAttrs[FaultID.ConstAssertion] = {cookBookRef: '142',}; faultsAttrs[FaultID.ImportAssertion] = {cookBookRef: '143',}; faultsAttrs[FaultID.LimitedStdLibApi] = {cookBookRef: '144',}; +faultsAttrs[FaultID.StrictDiagnostic] = {cookBookRef: '145',}; +faultsAttrs[FaultID.ErrorSuppression] = {cookBookRef: '146',}; +faultsAttrs[FaultID.UnsupportedDecorators] = {cookBookRef: '148',}; diff --git a/linter-4.2/src/TestRunner.ts b/linter-4.2/src/TestRunner.ts index a11921a59e523b79eb8fae7a16a6286021bcb51d..8a2a19e715a262351de208ebb27ff5246bca3937 100644 --- a/linter-4.2/src/TestRunner.ts +++ b/linter-4.2/src/TestRunner.ts @@ -16,7 +16,7 @@ import { TypeScriptLinter } from './TypeScriptLinter'; import { lint } from './LinterRunner'; import { parseCommandLine } from './CommandLineParser'; -import { Autofix, autofixInfo } from './Autofixer'; +import { Autofix } from './Autofixer'; import Logger from '../utils/logger'; import * as fs from 'node:fs'; import * as path from 'node:path'; @@ -104,10 +104,6 @@ function runTest(testDir: string, testFile: string, mode: Mode): boolean { } logger.info(`Running test ${testFile} (${Mode[mode]} mode)`); - // Clear node info collection and autofix settings from the previous test run. - TypeScriptLinter.problemsInfos = []; - autofixInfo.length = 0; - // Configure test parameters and run linter. const args: string[] = [path.join(testDir, testFile)]; if (mode === Mode.RELAX) args.push('--relax'); @@ -116,14 +112,19 @@ function runTest(testDir: string, testFile: string, mode: Mode): boolean { let autofixCfg = path.join(testDir, testFile + AUTOFIX_CONFIG_EXT); if (fs.existsSync(autofixCfg)) args.push(autofixCfg); } - lint({ cmdOptions: parseCommandLine(args) }); + const cmdOptions = parseCommandLine(args); + const result = lint({ cmdOptions: cmdOptions }); + const fileProblems = result.problemsInfos.get( path.normalize(cmdOptions.inputFiles[0]) ); + if (fileProblems === undefined) { + return true; + } const resultExt = RESULT_EXT[mode]; const testResultFileName = testFile + resultExt; // Get list of bad nodes from the current run. const resultNodes: TestNodeInfo[] = - TypeScriptLinter.problemsInfos.map( + fileProblems.map( (x) => ({ line: x.line, column: x.column, problem: x.problem, autofixable: mode === Mode.AUTOFIX ? x.autofixable : undefined, diff --git a/linter-4.2/src/TypeScriptLinter.ts b/linter-4.2/src/TypeScriptLinter.ts index ace430886a2d0790c9c3ffd5240653377b865904..8cd874d50d3b7069efe01dc616c99cd4d2c527b6 100644 --- a/linter-4.2/src/TypeScriptLinter.ts +++ b/linter-4.2/src/TypeScriptLinter.ts @@ -14,12 +14,15 @@ */ import * as ts from 'typescript'; -import * as Utils from './Utils'; +import * as path from 'node:path'; +import { TsUtils, getNodeOrLineEnd } from './Utils'; import { FaultID, faultsAttrs } from './Problems'; import { cookBookMsg, cookBookTag } from './CookBookMsg'; import { LinterConfig } from './TypeScriptLinterConfig'; -import { Autofix } from './Autofixer'; +import { Autofix, AutofixInfoSet } from './Autofixer'; import * as Autofixer from './Autofixer'; +import { ProblemInfo } from './ProblemInfo'; +import { ProblemSeverity } from './ProblemSeverity'; import Logger from '../utils/logger'; const logger = Logger.getLogger(); @@ -35,94 +38,44 @@ export function consoleLog(...args: any[]): void { logger.info(outLine); } -enum ProblemSeverity { WARNING = 1, ERROR = 2 } -export interface ProblemInfo { - line: number; - column: number; - start: number; - end: number; - type: string; - severity: number; - problem: string; - suggest: string; - rule: string; - ruleTag: number; - autofixable: boolean; - autofix?: Autofix[]; -} - export class TypeScriptLinter { - static ideMode: boolean; - static strictMode: boolean; - static logTscErrors: boolean; - static warningsAsErrors: boolean; - static totalVisitedNodes: number; - static nodeCounters: number[]; - static lineCounters: number[]; - - static totalErrorLines: number; - static errorLineNumbersString: string; - static totalWarningLines: number; - static warningLineNumbersString: string; - - // The SyntaxKind enum defines additional elements at the end of the enum - // that serve as markers (FirstX/LastX). Those elements are initialized - // with indices of the previously defined elements. As result, the enum - // may return incorrect name for a certain kind index (e.g. 'FirstStatement' - // instead of 'VariableStatement'). - // The following code creates a map with correct syntax kind names. - // It can be used when need to print name of syntax kind of certain - // AST node in diagnostic messages. - private static tsSyntaxKindNames: string[]; - - static problemsInfos: ProblemInfo[] = []; - - public static initStatic(): void { - TypeScriptLinter.ideMode = false; - TypeScriptLinter.strictMode = true; - TypeScriptLinter.logTscErrors = false; - TypeScriptLinter.warningsAsErrors = false; - TypeScriptLinter.totalVisitedNodes = 0; - TypeScriptLinter.nodeCounters = []; - TypeScriptLinter.lineCounters = []; - - TypeScriptLinter.totalErrorLines = 0; - TypeScriptLinter.totalWarningLines = 0; - TypeScriptLinter.errorLineNumbersString = ''; - TypeScriptLinter.warningLineNumbersString = ''; - - Autofixer.autofixInfo.length = 0; - - TypeScriptLinter.tsSyntaxKindNames = []; - const keys = Object.keys(ts.SyntaxKind); - const values = Object.values(ts.SyntaxKind); - - for (let i = 0; i < values.length; i++) { - const val = values[i]; - const kindNum = typeof val === 'string' ? parseInt(val) : val; - if (kindNum && !TypeScriptLinter.tsSyntaxKindNames[kindNum]) - TypeScriptLinter.tsSyntaxKindNames[kindNum] = keys[i]; - } + totalVisitedNodes: number = 0; + nodeCounters: number[] = []; + lineCounters: number[] = []; - for (let i = 0; i < FaultID.LAST_ID; i++) { - TypeScriptLinter.nodeCounters[i] = 0; - TypeScriptLinter.lineCounters[i] = 0; - } + totalErrorLines: number = 0; + errorLineNumbersString: string = ''; + totalWarningLines: number = 0; + warningLineNumbersString: string = ''; - TypeScriptLinter.problemsInfos = []; - } + problemsInfos: ProblemInfo[] = []; - static tsTypeChecker: ts.TypeChecker; + tsUtils: TsUtils; currentErrorLine: number; currentWarningLine: number; staticBlocks: Set; - constructor(private sourceFile: ts.SourceFile, private tsProgram: ts.Program) { - TypeScriptLinter.tsTypeChecker = tsProgram.getTypeChecker(); + private sourceFile?: ts.SourceFile; + + static ideMode: boolean = false; + + constructor( + private tsTypeChecker: ts.TypeChecker, + private autofixesInfo: AutofixInfoSet, + public strictMode: boolean, + public warningsAsErrors: boolean, + private tscStrictDiagnostics?: Map + ) { + this.tsUtils = new TsUtils(this.tsTypeChecker); this.currentErrorLine = 0; this.currentWarningLine = 0; this.staticBlocks = new Set(); + + for (let i = 0; i < FaultID.LAST_ID; i++) { + this.nodeCounters[i] = 0; + this.lineCounters[i] = 0; + } } readonly handlersMap = new Map([ @@ -158,28 +111,29 @@ export class TypeScriptLinter { [ts.SyntaxKind.ElementAccessExpression, this.handleElementAccessExpression], [ts.SyntaxKind.EnumMember, this.handleEnumMember], [ts.SyntaxKind.TypeReference, this.handleTypeReference], [ts.SyntaxKind.ExportDeclaration, this.handleExportDeclaration], - [ts.SyntaxKind.ExportSpecifier, this.handleExportSpecifier], [ts.SyntaxKind.ExportAssignment, this.handleExportAssignment], [ts.SyntaxKind.CallExpression, this.handleCallExpression], [ts.SyntaxKind.MetaProperty, this.handleMetaProperty], [ts.SyntaxKind.NewExpression, this.handleNewExpression], [ts.SyntaxKind.AsExpression, this.handleAsExpression], - [ts.SyntaxKind.BigIntLiteral, this.handleBigIntLiteral], [ts.SyntaxKind.SpreadElement, this.handleSpreadOp], [ts.SyntaxKind.SpreadAssignment, this.handleSpreadOp], [ts.SyntaxKind.NonNullExpression, this.handleNonNullExpression], - [ts.SyntaxKind.Constructor, this.handleConstructorDeclaration], + [ts.SyntaxKind.GetAccessor, this.handleGetAccessor], [ts.SyntaxKind.SetAccessor, this.handleSetAccessor], ]); - public incrementCounters(node: ts.Node, faultId: number, autofixable: boolean = false, autofix?: Autofix[]) { - if (!TypeScriptLinter.strictMode && faultsAttrs[faultId].migratable) // In relax mode skip migratable + public incrementCounters(node: ts.Node | ts.CommentRange, faultId: number, autofixable: boolean = false, autofix?: Autofix[],) { + if (!this.strictMode && faultsAttrs[faultId].migratable) // In relax mode skip migratable return; - TypeScriptLinter.nodeCounters[faultId]++; + const startPos = this.tsUtils.getStartPos(node); + const endPos = this.tsUtils.getEndPos(node); + + this.nodeCounters[faultId]++; // TSC counts lines and columns from zero - let { line, character } = this.sourceFile.getLineAndCharacterOfPosition(node.getStart()); + let { line, character } = this.sourceFile!.getLineAndCharacterOfPosition(startPos); ++line; ++character; let faultDescr = LinterConfig.nodeDesc[faultId]; - let faultType = TypeScriptLinter.tsSyntaxKindNames[node.kind]; + let faultType = LinterConfig.tsSyntaxKindNames[node.kind]; if (TypeScriptLinter.ideMode) { const cookBookMsgNum = faultsAttrs[faultId] ? Number(faultsAttrs[faultId].cookBookRef) : 0; @@ -187,11 +141,13 @@ export class TypeScriptLinter { let severity = ProblemSeverity.ERROR; if (faultsAttrs[faultId] && faultsAttrs[faultId].warning) severity = ProblemSeverity.WARNING; + const badNodeInfo: ProblemInfo = { line: line, column: character, - start: node.getStart(), - end: node.getEnd(), + endColumn: getNodeOrLineEnd(this.sourceFile!, startPos, endPos, line), + start: startPos, + end: endPos, type: faultType, severity: severity, problem: FaultID[faultId], @@ -199,44 +155,46 @@ export class TypeScriptLinter { rule: cookBookMsgNum > 0 && cookBookTg !== '' ? cookBookTg : faultDescr ? faultDescr : faultType, ruleTag: cookBookMsgNum, autofixable: autofixable, - autofix: autofix + autofix: autofix, }; - TypeScriptLinter.problemsInfos.push(badNodeInfo); + this.problemsInfos.push(badNodeInfo); } else { logger.info( - `Warning: ${this.sourceFile.fileName} (${line}, ${character}): ${faultDescr ? faultDescr : faultType}` + `Warning: ${this.sourceFile!.fileName} (${line}, ${character}): ${faultDescr ? faultDescr : faultType}` ); } - TypeScriptLinter.lineCounters[faultId]++; + this.lineCounters[faultId]++; if (faultsAttrs[faultId].warning) { if (line != this.currentWarningLine) { this.currentWarningLine = line; - ++TypeScriptLinter.totalWarningLines; - TypeScriptLinter.warningLineNumbersString += line + ', ' + ++this.totalWarningLines; + this.warningLineNumbersString += line + ', ' } } else if (line != this.currentErrorLine) { this.currentErrorLine = line; - ++TypeScriptLinter.totalErrorLines; - TypeScriptLinter.errorLineNumbersString += line + ', '; + ++this.totalErrorLines; + this.errorLineNumbersString += line + ', '; } } - public visitTSNode(node: ts.Node): void { + private visitTSNode(node: ts.Node): void { const self = this; visitTSNodeImpl(node); function visitTSNodeImpl(node: ts.Node): void { if (node === null || node.kind === null) return; - TypeScriptLinter.totalVisitedNodes++; + self.totalVisitedNodes++; - if (TypeScriptLinter.tsSyntaxKindNames[node.kind] === 'StructDeclaration') { + if (LinterConfig.tsSyntaxKindNames[node.kind] === 'StructDeclaration') { self.handleStructDeclaration(node); return; - } + } + + self.handleComments(node); if (LinterConfig.terminalTokens.has(node.kind)) return; @@ -292,7 +250,7 @@ export class TypeScriptLinter { // If specific declaration kind is provided, check against it. // Otherwise, use syntax kind of corresponding declaration node. - if (Utils.symbolHasDuplicateName(symbol, tsDeclKind ?? tsDeclNode.kind)) + if (this.tsUtils.symbolHasDuplicateName(symbol, tsDeclKind ?? tsDeclNode.kind)) this.incrementCounters(tsDeclNode, FaultID.DeclWithDuplicateName); } @@ -367,7 +325,7 @@ export class TypeScriptLinter { // Check whether base expression is 'any' type and its property // is being checked in runtime (i.e. expression appears as condition // of if/for/while, or is an operand of '&&', '||' or '!' operators). - const tsBaseExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(expr.expression); + const tsBaseExprType = this.tsTypeChecker.getTypeAtLocation(expr.expression); // Get parent node of the expression, pass through enclosing parentheses if needed. let exprParent = expr.parent; @@ -375,14 +333,14 @@ export class TypeScriptLinter { exprParent = exprParent.parent; return ( - Utils.isAnyType(tsBaseExprType) && + this.tsUtils.isAnyType(tsBaseExprType) && ( - (ts.isIfStatement(exprParent) && expr === Utils.unwrapParenthesized(exprParent.expression)) || - (ts.isWhileStatement(exprParent) && expr === Utils.unwrapParenthesized(exprParent.expression)) || - (ts.isDoStatement(exprParent) && expr === Utils.unwrapParenthesized(exprParent.expression)) || + (ts.isIfStatement(exprParent) && expr === this.tsUtils.unwrapParenthesized(exprParent.expression)) || + (ts.isWhileStatement(exprParent) && expr === this.tsUtils.unwrapParenthesized(exprParent.expression)) || + (ts.isDoStatement(exprParent) && expr === this.tsUtils.unwrapParenthesized(exprParent.expression)) || (ts.isForStatement(exprParent) && exprParent.condition && - expr === Utils.unwrapParenthesized(exprParent.condition)) || - (ts.isConditionalExpression(exprParent) && expr === Utils.unwrapParenthesized(exprParent.condition)) || + expr === this.tsUtils.unwrapParenthesized(exprParent.condition)) || + (ts.isConditionalExpression(exprParent) && expr === this.tsUtils.unwrapParenthesized(exprParent.condition)) || (ts.isBinaryExpression(exprParent) && (exprParent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || exprParent.operatorToken.kind === ts.SyntaxKind.BarBarToken)) || @@ -392,9 +350,9 @@ export class TypeScriptLinter { } private isIIFEasNamespace(tsExpr: ts.PropertyAccessExpression): boolean { - const nameSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsExpr.name); + const nameSymbol = this.tsTypeChecker.getSymbolAtLocation(tsExpr.name); if (!nameSymbol) { - const leftHandSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsExpr.expression); + const leftHandSymbol = this.tsTypeChecker.getSymbolAtLocation(tsExpr.expression); if (leftHandSymbol) { const decls = leftHandSymbol.getDeclarations(); if (!decls || decls.length !== 1) return false; @@ -405,7 +363,7 @@ export class TypeScriptLinter { const varDecl = leftHandDecl as ts.VariableDeclaration; if (varDecl.initializer && ts.isCallExpression(varDecl.initializer)) { const callExpr = varDecl.initializer as ts.CallExpression; - const expr = Utils.unwrapParenthesized(callExpr.expression); + const expr = this.tsUtils.unwrapParenthesized(callExpr.expression); if (ts.isFunctionExpression(expr)) return true; } } @@ -419,25 +377,25 @@ export class TypeScriptLinter { return false; // Check if property symbol is 'Prototype' - const propAccessSym = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsPropertyAccess); - if (Utils.isPrototypeSymbol(propAccessSym)) return true; + const propAccessSym = this.tsTypeChecker.getSymbolAtLocation(tsPropertyAccess); + if (this.tsUtils.isPrototypeSymbol(propAccessSym)) return true; // Check if symbol of LHS-expression is Class or Function. const tsBaseExpr = tsPropertyAccess.expression; - const baseExprSym = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsBaseExpr); - if (Utils.isTypeSymbol(baseExprSym) || Utils.isFunctionSymbol(baseExprSym)) + const baseExprSym = this.tsTypeChecker.getSymbolAtLocation(tsBaseExpr); + if (this.tsUtils.isTypeSymbol(baseExprSym) || this.tsUtils.isFunctionSymbol(baseExprSym)) return true; // Check if type of LHS expression Function type or Any type. // The latter check is to cover cases with multiple prototype // chain (as the 'Prototype' property should be 'Any' type): // X.prototype.prototype.prototype = ... - const baseExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsBaseExpr); - const baseExprTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode( + const baseExprType = this.tsTypeChecker.getTypeAtLocation(tsBaseExpr); + const baseExprTypeNode = this.tsTypeChecker.typeToTypeNode( baseExprType, undefined, ts.NodeBuilderFlags.None ); - return ((baseExprTypeNode && ts.isFunctionTypeNode(baseExprTypeNode)) || Utils.isAnyType(baseExprType)); + return ((baseExprTypeNode && ts.isFunctionTypeNode(baseExprTypeNode)) || this.tsUtils.isAnyType(baseExprType)); } private interfaceInharitanceLint(node: ts.Node, heritageClauses: ts.NodeArray): void { @@ -446,7 +404,7 @@ export class TypeScriptLinter { const prop2type = new Map(); for (const tsTypeExpr of hClause.types) { - const tsExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); + const tsExprType = this.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); if (tsExprType.isClass()) this.incrementCounters(node, FaultID.InterfaceExtendsClass); else if (tsExprType.isClassOrInterface()) @@ -487,18 +445,18 @@ export class TypeScriptLinter { let objectLiteralExpr = node as ts.ObjectLiteralExpression; // If object literal is a part of destructuring assignment, then don't process it further. - if (Utils.isDestructuringAssignmentLHS(objectLiteralExpr)) + if (this.tsUtils.isDestructuringAssignmentLHS(objectLiteralExpr)) return; - let objectLiteralType = TypeScriptLinter.tsTypeChecker.getContextualType(objectLiteralExpr); - if (!Utils.areTypesAssignable(objectLiteralType, objectLiteralExpr)) + let objectLiteralType = this.tsTypeChecker.getContextualType(objectLiteralExpr); + if (!this.tsUtils.areTypesAssignable(objectLiteralType, objectLiteralExpr)) this.incrementCounters(node, FaultID.ObjectLiteralNoContextType); } private handleArrayLiteralExpression(node: ts.Node) { // If array literal is a part of destructuring assignment, then // don't process it further. - if (Utils.isDestructuringAssignmentLHS(node as ts.ArrayLiteralExpression)) return; + if (this.tsUtils.isDestructuringAssignmentLHS(node as ts.ArrayLiteralExpression)) return; let arrayLitNode = node as ts.ArrayLiteralExpression; @@ -512,8 +470,8 @@ export class TypeScriptLinter { let arrayLitElements = arrayLitNode.elements; for(let element of arrayLitElements ) { if( element.kind === ts.SyntaxKind.ObjectLiteralExpression ) { - let objectLiteralType = TypeScriptLinter.tsTypeChecker.getContextualType(element); - if ( !Utils.validateObjectLiteralType(objectLiteralType) ) { + let objectLiteralType = this.tsTypeChecker.getContextualType(element); + if ( !this.tsUtils.validateObjectLiteralType(objectLiteralType) ) { noContextTypeForArrayLiteral = true; break; } @@ -528,15 +486,26 @@ export class TypeScriptLinter { let tsParam = node as ts.ParameterDeclaration; if (ts.isArrayBindingPattern(tsParam.name) || ts.isObjectBindingPattern(tsParam.name)) this.incrementCounters(node, FaultID.DestructuringParameter); + + let tsParamMods = tsParam.modifiers; + if ( + tsParamMods && + (this.tsUtils.hasModifier(tsParamMods, ts.SyntaxKind.PublicKeyword) || + this.tsUtils.hasModifier(tsParamMods, ts.SyntaxKind.ProtectedKeyword) || + this.tsUtils.hasModifier(tsParamMods, ts.SyntaxKind.PrivateKeyword)) + ) + this.incrementCounters(node, FaultID.ParameterProperties); + + this.handleDecorators(tsParam.decorators); } private handleEnumDeclaration(node: ts.Node) { let enumNode = node as ts.EnumDeclaration; this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(enumNode.name), enumNode + this.tsTypeChecker.getSymbolAtLocation(enumNode.name), enumNode ); - let enumSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(enumNode.name); + let enumSymbol = this.tsTypeChecker.getSymbolAtLocation(enumNode.name); if (!enumSymbol) return; let enumDecls = enumSymbol.getDeclarations(); @@ -556,7 +525,7 @@ export class TypeScriptLinter { private handleInterfaceDeclaration(node: ts.Node) { let interfaceNode = node as ts.InterfaceDeclaration; - let iSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(interfaceNode.name); + let iSymbol = this.tsTypeChecker.getSymbolAtLocation(interfaceNode.name); let iDecls = iSymbol ? iSymbol.getDeclarations() : null; if (iDecls) { // Since type checker merges all declarations with the same name @@ -573,25 +542,20 @@ export class TypeScriptLinter { if (interfaceNode.heritageClauses) this.interfaceInharitanceLint(node, interfaceNode.heritageClauses); - for (const typeElem of interfaceNode.members) { - // ArkTs does not support otional properties of primitive types. - if (typeElem.questionToken && ts.isPropertySignature(typeElem)) { - let type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(typeElem.name); - if (type && Utils.isPrimitiveType(type)) - this.incrementCounters(typeElem, FaultID.InterfaceOptionalProp); - } - } - this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(interfaceNode.name), interfaceNode + this.tsTypeChecker.getSymbolAtLocation(interfaceNode.name), interfaceNode ); } private handleThrowStatement(node: ts.Node) { let throwStmt = node as ts.ThrowStatement; - let throwExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(throwStmt.expression); - if (!throwExprType.isClassOrInterface() || !this.typeHierarchyHasTypeError(throwExprType)) - this.incrementCounters(node, FaultID.ThrowStatement); + let throwExprType = this.tsTypeChecker.getTypeAtLocation(throwStmt.expression); + if (!throwExprType.isClassOrInterface() || !this.typeHierarchyHasTypeError(throwExprType)) { + let autofix: Autofix[] | undefined; + if (this.autofixesInfo.shouldAutofix(throwStmt, FaultID.ThrowStatement)) + autofix = [ Autofixer.wrapExpressionInError(throwStmt.expression, this.tsUtils.isStringType(throwExprType)) ]; + this.incrementCounters(node, FaultID.ThrowStatement, true, autofix); + } } private handleForStatement(node: ts.Node) { @@ -616,12 +580,12 @@ export class TypeScriptLinter { this.incrementCounters(tsForOfInit, FaultID.DestructuringAssignment); let expr = tsForOfStmt.expression; - let exprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(expr); - let exprTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode( + let exprType = this.tsTypeChecker.getTypeAtLocation(expr); + let exprTypeNode = this.tsTypeChecker.typeToTypeNode( exprType, undefined, ts.NodeBuilderFlags.None ); - if (!(ts.isArrayLiteralExpression(expr) || Utils.isArrayNotTupleType(exprTypeNode))) + if (!(ts.isArrayLiteralExpression(expr) || this.tsUtils.isArrayNotTupleType(exprTypeNode))) this.incrementCounters(node, FaultID.ForOfNonArray); } @@ -630,7 +594,7 @@ export class TypeScriptLinter { if (fullText.startsWith('keyof')) this.incrementCounters(node, FaultID.KeyOfOperator); else if (fullText.startsWith('readonly')) - this.incrementCounters(node, FaultID.ReadonlyArr); + this.incrementCounters(node, FaultID.ReadonlyArr, true, [ Autofixer.fixReadonlyArr(node) ]); } private handleImportDeclaration(node: ts.Node) { @@ -651,23 +615,63 @@ export class TypeScriptLinter { if (this.isIIFEasNamespace(propertyAccessNode)) this.incrementCounters(node, FaultID.IifeAsNamespace); if (this.isPrototypePropertyAccess(propertyAccessNode)) this.incrementCounters(propertyAccessNode.name, FaultID.Prototype); - let symbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(propertyAccessNode); - if( !!symbol && Utils.isSymbolAPI(symbol)) + let symbol = this.tsTypeChecker.getSymbolAtLocation(propertyAccessNode); + if( !!symbol && this.tsUtils.isSymbolAPI(symbol)) this.incrementCounters(node, FaultID.SymbolType); } private handlePropertyAssignmentOrDeclaration(node: ts.Node) { - let prop = (node as ts.PropertyAssignment | ts.PropertyDeclaration).name; - - if (prop && (prop.kind === ts.SyntaxKind.NumericLiteral || prop.kind === ts.SyntaxKind.StringLiteral)) { - let autofix : Autofix[] | undefined; - let autofixable = true; - if (Autofixer.shouldAutofix(node, FaultID.LiteralAsPropertyName)) { - autofix = Autofixer.fixLiteralAsPropertyName(node); - autofixable = autofix != undefined; + let propName = (node as ts.PropertyAssignment | ts.PropertyDeclaration).name; + + if (propName && (propName.kind === ts.SyntaxKind.NumericLiteral || propName.kind === ts.SyntaxKind.StringLiteral)) { + // We can use literals as property names only when creating Record instances. + let isRecordObjectInitializer = false; + if (ts.isPropertyAssignment(node)) { + let objectLiteralType = this.tsTypeChecker.getContextualType(node.parent); + isRecordObjectInitializer = !!objectLiteralType && this.tsUtils.isStdRecordType(objectLiteralType); } - this.incrementCounters(node, FaultID.LiteralAsPropertyName, autofixable, autofix); + if (!isRecordObjectInitializer) { + let autofix : Autofix[] | undefined = Autofixer.fixLiteralAsPropertyName(node); + let autofixable = autofix != undefined; + if (!this.autofixesInfo.shouldAutofix(node, FaultID.LiteralAsPropertyName)) { + autofix = undefined; + } + this.incrementCounters(node, FaultID.LiteralAsPropertyName, autofixable, autofix); + } + } + + if (ts.isPropertyDeclaration(node)) { + const decorators = node.decorators; + this.handleDecorators(decorators); + this.filterOutStrictDiagnostics(decorators, propName); + } + } + + private filterOutStrictDiagnostics(decorators: readonly ts.Decorator[] | undefined, propName: ts.PropertyName) { + // Filter out non-initializable property decorators from strict diagnostics. + if (this.tscStrictDiagnostics && this.sourceFile) { + if (decorators?.some(x => { + let decoratorName = ''; + if (ts.isIdentifier(x.expression)) + decoratorName = x.expression.text; + else if (ts.isCallExpression(x.expression) && ts.isIdentifier(x.expression.expression)) + decoratorName = x.expression.expression.text; + + return TsUtils.NON_INITIALIZABLE_PROPERTY_DECORATORS.includes(decoratorName); + })) { + let file = path.normalize(this.sourceFile.fileName); + let tscDiagnostics = this.tscStrictDiagnostics.get(file) + if (tscDiagnostics) { + let filteredDiagnostics = tscDiagnostics.filter( + (val, idx, array) => !( + val.code === TsUtils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE && + val.start === propName.getStart() + ) + ); + this.tscStrictDiagnostics.set(file, filteredDiagnostics); + } + } } } @@ -685,7 +689,7 @@ export class TypeScriptLinter { newParams && !hasUnfixableReturnType; let autofix: Autofix[] | undefined; - if (autofixable && Autofixer.shouldAutofix(node, FaultID.FunctionExpression)) { + if (autofixable && this.autofixesInfo.shouldAutofix(node, FaultID.FunctionExpression)) { autofix = [ Autofixer.fixFunctionExpression(funcExpr, newParams, newRetTypeNode) ]; } @@ -718,17 +722,17 @@ export class TypeScriptLinter { if (param.type) continue; hasOmittedType = true; - let paramType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(param); - let paramTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(paramType, param, ts.NodeBuilderFlags.None); - if (!paramType || Utils.isUnsupportedType(paramType) || !paramTypeNode) { + let paramType = this.tsTypeChecker.getTypeAtLocation(param); + let paramTypeNode = this.tsTypeChecker.typeToTypeNode(paramType, param, ts.NodeBuilderFlags.None); + if (!paramType || this.tsUtils.isUnsupportedType(paramType) || !paramTypeNode) { autofixable = false; continue; } - + if (isFuncExpr) { let newParam = Autofixer.fixParamWithoutType(param, paramTypeNode, true) as ts.ParameterDeclaration; newParams[newParams.length-1] = newParam; - } else if (Autofixer.shouldAutofix(signDecl, FaultID.ArrowFunctionWithOmittedTypes)) { + } else if (this.autofixesInfo.shouldAutofix(signDecl, FaultID.ArrowFunctionWithOmittedTypes)) { if (!autofix) autofix = []; autofix.push(Autofixer.fixParamWithoutType(param, paramTypeNode) as Autofix); } @@ -744,6 +748,7 @@ export class TypeScriptLinter { private handleClassExpression(node: ts.Node) { let tsClassExpr = node as ts.ClassExpression; this.incrementCounters(node, FaultID.ClassExpression); + this.handleDecorators(tsClassExpr.decorators); } private handleFunctionDeclaration(node: ts.Node) { @@ -751,13 +756,9 @@ export class TypeScriptLinter { if (!tsFunctionDeclaration.type) this.handleMissingReturnType(tsFunctionDeclaration); if (tsFunctionDeclaration.name) this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsFunctionDeclaration.name), tsFunctionDeclaration + this.tsTypeChecker.getSymbolAtLocation(tsFunctionDeclaration.name), tsFunctionDeclaration ); - let tsParams = tsFunctionDeclaration.parameters; - for (const tsParam of tsParams) - if (tsParam.questionToken) this.handleOptionalParam(tsParam); - if (tsFunctionDeclaration.body && this.functionContainsThis(tsFunctionDeclaration.body)) this.incrementCounters(node, FaultID.FunctionContainsThis); @@ -768,8 +769,6 @@ export class TypeScriptLinter { } private handleMissingReturnType(funcLikeDecl: ts.FunctionLikeDeclaration): [boolean, ts.TypeNode | undefined] { - if (funcLikeDecl.type) return [false, funcLikeDecl.type]; - // Note: Return type can't be inferred for function without body. if (!funcLikeDecl.body) return [false, undefined]; @@ -783,18 +782,20 @@ export class TypeScriptLinter { // value type is omitted. In that case, we attempt to prepare an autofix. let hasLimitedRetTypeInference = this.hasLimitedTypeInferenceFromReturnExpr(funcLikeDecl.body); - let tsSignature = TypeScriptLinter.tsTypeChecker.getSignatureFromDeclaration(funcLikeDecl); + let tsSignature = this.tsTypeChecker.getSignatureFromDeclaration(funcLikeDecl); if (tsSignature) { - let tsRetType = TypeScriptLinter.tsTypeChecker.getReturnTypeOfSignature(tsSignature); + let tsRetType = this.tsTypeChecker.getReturnTypeOfSignature(tsSignature); - if (!tsRetType || Utils.isUnsupportedType(tsRetType)) { + if (!tsRetType || this.tsUtils.isUnsupportedType(tsRetType)) { hasLimitedRetTypeInference = true; } else if (hasLimitedRetTypeInference) { - newRetTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(tsRetType, funcLikeDecl, ts.NodeBuilderFlags.None); - autofixable = !!newRetTypeNode; - - if (!isFuncExpr && newRetTypeNode && Autofixer.shouldAutofix(funcLikeDecl, FaultID.LimitedReturnTypeInference)) - autofix = [Autofixer.fixReturnType(funcLikeDecl, newRetTypeNode)]; + newRetTypeNode = this.tsTypeChecker.typeToTypeNode(tsRetType, funcLikeDecl, ts.NodeBuilderFlags.None); + if (newRetTypeNode && !isFuncExpr) { + autofixable = true; + if (this.autofixesInfo.shouldAutofix(funcLikeDecl, FaultID.LimitedReturnTypeInference)) { + autofix = [Autofixer.fixReturnType(funcLikeDecl, newRetTypeNode)]; + } + } } } @@ -808,12 +809,13 @@ export class TypeScriptLinter { private hasLimitedTypeInferenceFromReturnExpr(funBody: ts.ConciseBody): boolean { let hasLimitedTypeInference = false; + const self = this; function visitNode(tsNode: ts.Node): void { if (hasLimitedTypeInference) return; if ( ts.isReturnStatement(tsNode) && tsNode.expression && - Utils.isCallToFunctionWithOmittedReturnType(Utils.unwrapParenthesized(tsNode.expression)) + self.tsUtils.isCallToFunctionWithOmittedReturnType(self.tsUtils.unwrapParenthesized(tsNode.expression)) ) { hasLimitedTypeInference = true; return; @@ -833,20 +835,13 @@ export class TypeScriptLinter { if (ts.isBlock(funBody)) { visitNode(funBody); } else { - const tsExpr = Utils.unwrapParenthesized(funBody); - hasLimitedTypeInference = Utils.isCallToFunctionWithOmittedReturnType(tsExpr); + const tsExpr = this.tsUtils.unwrapParenthesized(funBody); + hasLimitedTypeInference = this.tsUtils.isCallToFunctionWithOmittedReturnType(tsExpr); } return hasLimitedTypeInference; } - private handleOptionalParam(tsParam: ts.ParameterDeclaration) { - const paramType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsParam); - if (Utils.isNumberLikeType(paramType) || Utils.isBooleanType(paramType)) { - this.incrementCounters(tsParam, FaultID.FuncOptionalParams); - } - } - private handlePrefixUnaryExpression(node: ts.Node) { let tsUnaryArithm = node as ts.PrefixUnaryExpression; let tsUnaryOp = tsUnaryArithm.operator; @@ -855,11 +850,11 @@ export class TypeScriptLinter { tsUnaryOp === ts.SyntaxKind.MinusToken || tsUnaryOp === ts.SyntaxKind.TildeToken ) { - const tsOperatndType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsUnaryArithm.operand); - if (!(tsOperatndType.getFlags() & ts.TypeFlags.NumberLike)|| - ( tsUnaryOp === ts.SyntaxKind.TildeToken && tsUnaryArithm.operand.kind === ts.SyntaxKind.NumericLiteral && - !Utils.isIntegerConstantValue(tsUnaryArithm.operand as ts.NumericLiteral) ) - ) + const tsOperatndType = this.tsTypeChecker.getTypeAtLocation(tsUnaryArithm.operand); + if (!(tsOperatndType.getFlags() & (ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLiteral)) || + (tsUnaryOp === ts.SyntaxKind.TildeToken && tsUnaryArithm.operand.kind === ts.SyntaxKind.NumericLiteral && + !this.tsUtils.isIntegerConstantValue(tsUnaryArithm.operand as ts.NumericLiteral) ) + ) this.incrementCounters(node, FaultID.UnaryArithmNotNumber); } } @@ -869,28 +864,36 @@ export class TypeScriptLinter { let tsLhsExpr = tsBinaryExpr.left; let tsRhsExpr = tsBinaryExpr.right; - if (Utils.isAssignmentOperator(tsBinaryExpr.operatorToken)) { + if (this.tsUtils.isAssignmentOperator(tsBinaryExpr.operatorToken)) { if (ts.isObjectLiteralExpression(tsLhsExpr) || ts.isArrayLiteralExpression(tsLhsExpr)) this.incrementCounters(node, FaultID.DestructuringAssignment); if (ts.isPropertyAccessExpression(tsLhsExpr)) { - const tsLhsSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsLhsExpr); - const tsLhsBaseSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsLhsExpr.expression); + const tsLhsSymbol = this.tsTypeChecker.getSymbolAtLocation(tsLhsExpr); + const tsLhsBaseSymbol = this.tsTypeChecker.getSymbolAtLocation(tsLhsExpr.expression); if ( - Utils.isMethodAssignment(tsLhsSymbol) && tsLhsBaseSymbol && + this.tsUtils.isMethodAssignment(tsLhsSymbol) && tsLhsBaseSymbol && (tsLhsBaseSymbol.flags & ts.SymbolFlags.Function) !== 0 ) this.incrementCounters(tsLhsExpr, FaultID.PropertyDeclOnFunction); } } - let leftOperandType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsLhsExpr); - let rightOperandType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsRhsExpr); + let leftOperandType = this.tsTypeChecker.getTypeAtLocation(tsLhsExpr); + let rightOperandType = this.tsTypeChecker.getTypeAtLocation(tsRhsExpr); if (tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) { - if (Utils.isNumberType(leftOperandType) && Utils.isNumberType(rightOperandType)) + if (this.tsUtils.isEnumMemberType(leftOperandType) && this.tsUtils.isEnumMemberType(rightOperandType)) { + if ( + ( (leftOperandType.getFlags() & (ts.TypeFlags.NumberLike)) && (rightOperandType.getFlags() & (ts.TypeFlags.NumberLike ) ) ) || + ( (leftOperandType.getFlags() & (ts.TypeFlags.StringLike)) && (rightOperandType.getFlags() & (ts.TypeFlags.StringLike ) ) ) + ) + return; + else + this.incrementCounters(node, FaultID.AddWithWrongType); + } else if (this.tsUtils.isNumberType(leftOperandType) && this.tsUtils.isNumberType(rightOperandType)) return; - else if (Utils.isStringType(leftOperandType) || Utils.isStringType(rightOperandType)) + else if (this.tsUtils.isStringType(leftOperandType) || this.tsUtils.isStringType(rightOperandType)) return; else this.incrementCounters(node, FaultID.AddWithWrongType); @@ -902,11 +905,11 @@ export class TypeScriptLinter { tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.GreaterThanGreaterThanToken || tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken ) { - if (!(Utils.isNumberType(leftOperandType) && Utils.isNumberType(rightOperandType))|| - ( tsLhsExpr.kind === ts.SyntaxKind.NumericLiteral && !Utils.isIntegerConstantValue(tsLhsExpr as ts.NumericLiteral)) || - ( tsRhsExpr.kind === ts.SyntaxKind.NumericLiteral && !Utils.isIntegerConstantValue(tsRhsExpr as ts.NumericLiteral)) - ) - this.incrementCounters(node, FaultID.BitOpWithWrongType); + if (!(this.tsUtils.isNumberType(leftOperandType) && this.tsUtils.isNumberType(rightOperandType)) || + ( tsLhsExpr.kind === ts.SyntaxKind.NumericLiteral && !this.tsUtils.isIntegerConstantValue(tsLhsExpr as ts.NumericLiteral)) || + ( tsRhsExpr.kind === ts.SyntaxKind.NumericLiteral && !this.tsUtils.isIntegerConstantValue(tsRhsExpr as ts.NumericLiteral)) + ) + return; // FaultID.BitOpWithWrongType -removed as rule #61 } else if (tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.CommaToken) { // CommaOpertor is allowed in 'for' statement initalizer and incrementor let tsExprNode: ts.Node = tsBinaryExpr; @@ -922,24 +925,19 @@ export class TypeScriptLinter { } this.incrementCounters(node, FaultID.CommaOperator); } else if (tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) { - const leftExpr = Utils.unwrapParenthesized(tsBinaryExpr.left); - const leftSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(leftExpr); + const leftExpr = this.tsUtils.unwrapParenthesized(tsBinaryExpr.left); + const leftSymbol = this.tsTypeChecker.getSymbolAtLocation(leftExpr); // In STS, the left-hand side expression may be of any reference type, otherwise // a compile-time error occurs. In addition, the left operand in STS cannot be a type. - if (ts.isTypeNode(leftExpr) || !Utils.isReferenceType(leftOperandType) || Utils.isTypeSymbol(leftSymbol)) + if (ts.isTypeNode(leftExpr) || !this.tsUtils.isReferenceType(leftOperandType) || this.tsUtils.isTypeSymbol(leftSymbol)) this.incrementCounters(node, FaultID.InstanceofUnsupported); } else if (tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.EqualsToken) { if ( leftOperandType.isClassOrInterface() && rightOperandType.isClassOrInterface() && - !Utils.relatedByInheritanceOrIdentical(rightOperandType, leftOperandType) + !this.tsUtils.relatedByInheritanceOrIdentical(rightOperandType, leftOperandType) ) this.incrementCounters(tsBinaryExpr, FaultID.StructuralIdentity); - if ( - ts.isIdentifier(tsRhsExpr) && tsRhsExpr.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword && - (!leftOperandType.isUnion() || Utils.isUnsupportedUnionType(leftOperandType)) - ) - this.incrementCounters(tsRhsExpr, FaultID.UndefinedValue); } } @@ -960,7 +958,7 @@ export class TypeScriptLinter { if (ts.isIdentifier(tsBindingName)) // The syntax kind of the declaration is defined here by the parent of 'BindingName' node. this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsBindingName), tsBindingName, + this.tsTypeChecker.getSymbolAtLocation(tsBindingName), tsBindingName, tsBindingName.parent.kind ); else { @@ -979,36 +977,35 @@ export class TypeScriptLinter { if (tsVarDecl.type && tsVarDecl.initializer) { let tsVarInit = tsVarDecl.initializer; - let tsVarType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsVarDecl.type); - let tsInitType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsVarInit); + let tsVarType = this.tsTypeChecker.getTypeAtLocation(tsVarDecl.type); + let tsInitType = this.tsTypeChecker.getTypeAtLocation(tsVarInit); if ( tsVarType.isClassOrInterface() && tsInitType.isClassOrInterface() && - !Utils.relatedByInheritanceOrIdentical(tsInitType, tsVarType) + !this.tsUtils.relatedByInheritanceOrIdentical(tsInitType, tsVarType) ) this.incrementCounters(tsVarDecl, FaultID.StructuralIdentity); - if ( - ts.isIdentifier(tsVarInit) && tsVarInit.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword && - (!tsVarType.isUnion() || Utils.isUnsupportedUnionType(tsVarType)) - ) - this.incrementCounters(tsVarDecl.initializer, FaultID.UndefinedValue); } } private handleCatchClause(node: ts.Node) { let tsCatch = node as ts.CatchClause; // In TS catch clause doesn't permit specification of the exception varible type except 'any' or 'unknown'. - // It is not compatible with STS 'catch' where the exception varilab has to be of type - // 'Exception' or derived from it. - // So each 'catch' which has explicite type for the exception object goes to problems in strict mode. - if (tsCatch.variableDeclaration && tsCatch.variableDeclaration.type) - this.incrementCounters(node, FaultID.CatchWithUnsupportedType); + // It is not compatible with STS 'catch' where the exception variable has to be of type + // Error or derived from it. + // So each 'catch' which has explicit type for the exception object goes to problems in strict mode. + if (tsCatch.variableDeclaration && tsCatch.variableDeclaration.type) { + let autofix: Autofix[] | undefined; + if (this.autofixesInfo.shouldAutofix(tsCatch, FaultID.CatchWithUnsupportedType)) + autofix = [ Autofixer.dropTypeOnVarDecl(tsCatch.variableDeclaration) ]; + this.incrementCounters(node, FaultID.CatchWithUnsupportedType, true, autofix); + } } private handleClassDeclaration(node: ts.Node) { let tsClassDecl = node as ts.ClassDeclaration; if (tsClassDecl.name) this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsClassDecl.name), + this.tsTypeChecker.getSymbolAtLocation(tsClassDecl.name), tsClassDecl ); @@ -1018,18 +1015,20 @@ export class TypeScriptLinter { for (const hClause of tsClassDecl.heritageClauses) { if (hClause && hClause.token === ts.SyntaxKind.ImplementsKeyword) { for (const tsTypeExpr of hClause.types) { - const tsExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); + const tsExprType = this.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression); if (tsExprType.isClass()) this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass); } } } } + + this.handleDecorators(tsClassDecl.decorators); } private handleModuleDeclaration(node: ts.Node) { let tsModuleDecl = node as ts.ModuleDeclaration; this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsModuleDecl.name), + this.tsTypeChecker.getSymbolAtLocation(tsModuleDecl.name), tsModuleDecl ); @@ -1056,7 +1055,7 @@ export class TypeScriptLinter { } } } - } else if (Utils.hasModifier(tsModifiers, ts.SyntaxKind.DeclareKeyword)) { + } else if (this.tsUtils.hasModifier(tsModifiers, ts.SyntaxKind.DeclareKeyword)) { this.incrementCounters(tsModuleDecl, FaultID.ShorthandAmbientModuleDecl); } @@ -1067,7 +1066,7 @@ export class TypeScriptLinter { private handleTypeAliasDeclaration(node: ts.Node) { let tsTypeAlias = node as ts.TypeAliasDeclaration; this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsTypeAlias.name), tsTypeAlias + this.tsTypeChecker.getSymbolAtLocation(tsTypeAlias.name), tsTypeAlias ); } @@ -1075,27 +1074,44 @@ export class TypeScriptLinter { let tsImportClause = node as ts.ImportClause; if (tsImportClause.name) { this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsImportClause.name), tsImportClause + this.tsTypeChecker.getSymbolAtLocation(tsImportClause.name), tsImportClause ); } - if (tsImportClause.isTypeOnly) this.incrementCounters(node, FaultID.TypeOnlyImport); + if (tsImportClause.namedBindings && ts.isNamedImports(tsImportClause.namedBindings)) { + let nonDefaultSpecs: ts.ImportSpecifier[] = []; + let defaultSpec: ts.ImportSpecifier | undefined; + for (const importSpec of tsImportClause.namedBindings.elements) { + if (this.tsUtils.isDefaultImport(importSpec)) defaultSpec = importSpec; + else nonDefaultSpecs.push(importSpec); + } + if (defaultSpec) { + let autofix: Autofix[] | undefined; + if (this.autofixesInfo.shouldAutofix(defaultSpec, FaultID.DefaultImport)) + autofix = [ Autofixer.fixDefaultImport(tsImportClause, defaultSpec, nonDefaultSpecs) ]; + this.incrementCounters(defaultSpec, FaultID.DefaultImport, true, autofix); + } + } + + if (tsImportClause.isTypeOnly) { + let autofix: Autofix[] | undefined; + if (this.autofixesInfo.shouldAutofix(node, FaultID.TypeOnlyImport)) + autofix = [ Autofixer.dropTypeOnlyFlag(tsImportClause) ]; + this.incrementCounters(node, FaultID.TypeOnlyImport, true, autofix); + } } private handleImportSpecifier(node: ts.Node) { - let tsImportSpecifier = node as ts.ImportSpecifier; + let importSpec = node as ts.ImportSpecifier; this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsImportSpecifier.name), tsImportSpecifier + this.tsTypeChecker.getSymbolAtLocation(importSpec.name), importSpec ); - - if (tsImportSpecifier.propertyName && tsImportSpecifier.propertyName.text === 'default') - this.incrementCounters(node, FaultID.DefaultImport); } private handleNamespaceImport(node: ts.Node) { let tsNamespaceImport = node as ts.NamespaceImport; this.countDeclarationsWithDuplicateName( - TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsNamespaceImport.name), tsNamespaceImport + this.tsTypeChecker.getSymbolAtLocation(tsNamespaceImport.name), tsNamespaceImport ); } @@ -1104,31 +1120,35 @@ export class TypeScriptLinter { if (tsTypeAssertion.type.getText() === 'const') this.incrementCounters(tsTypeAssertion, FaultID.ConstAssertion); else - this.incrementCounters(node, FaultID.TypeAssertion); + this.incrementCounters(node, FaultID.TypeAssertion, true, [ Autofixer.fixTypeAssertion(tsTypeAssertion) ]); } private handleMethodDeclaration(node: ts.Node) { let tsMethodDecl = node as ts.MethodDeclaration; + if (!tsMethodDecl.type) this.handleMissingReturnType(tsMethodDecl); + if (tsMethodDecl.asteriskToken) this.incrementCounters(node, FaultID.GeneratorFunction); + + this.handleDecorators(tsMethodDecl.decorators); } private handleSwitchStatement(node: ts.Node) { let tsSwitchStmt = node as ts.SwitchStatement; - let tsSwitchExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsSwitchStmt.expression); + let tsSwitchExprType = this.tsTypeChecker.getTypeAtLocation(tsSwitchStmt.expression); if ( !(tsSwitchExprType.getFlags() & (ts.TypeFlags.NumberLike | ts.TypeFlags.StringLike)) && - !Utils.isEnumType(tsSwitchExprType) + !this.tsUtils.isEnumType(tsSwitchExprType) ) this.incrementCounters(tsSwitchStmt.expression, FaultID.SwitchSelectorInvalidType); for (const tsCaseClause of tsSwitchStmt.caseBlock.clauses) { if (ts.isCaseClause(tsCaseClause)) { const tsCaseExpr = tsCaseClause.expression; - const tsCaseExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsCaseExpr); + const tsCaseExprType = this.tsTypeChecker.getTypeAtLocation(tsCaseExpr); if ( !( ts.isNumericLiteral(tsCaseExpr) || @@ -1143,7 +1163,7 @@ export class TypeScriptLinter { private handleIdentifier(node: ts.Node) { let tsIdentifier = node as ts.Identifier; - let tsIdentSym = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsIdentifier); + let tsIdentSym = this.tsTypeChecker.getSymbolAtLocation(tsIdentifier); if (tsIdentSym) { this.handleNamespaceAsObject(tsIdentifier, tsIdentSym); @@ -1155,7 +1175,7 @@ export class TypeScriptLinter { ) this.incrementCounters(node, FaultID.GlobalThis); - if (Utils.isGlobalSymbol(tsIdentSym) && Utils.LIMITED_STD_GLOBAL_VAR.includes(tsIdentSym.getName())) + if (this.tsUtils.isGlobalSymbol(tsIdentSym) && TsUtils.LIMITED_STD_GLOBAL_VAR.includes(tsIdentSym.getName())) this.incrementCounters(node, FaultID.LimitedStdLibApi); } } @@ -1169,7 +1189,7 @@ export class TypeScriptLinter { ) { // If module name is duplicated by another declaration, this increases the possibility // of finding a lot of false positives. Thus, do not check further in that case. - if (!Utils.symbolHasDuplicateName(tsIdentSym, ts.SyntaxKind.ModuleDeclaration)) { + if (!this.tsUtils.symbolHasDuplicateName(tsIdentSym, ts.SyntaxKind.ModuleDeclaration)) { // If module name is the right-most name of Property Access chain or Qualified name, // or it's a separate identifier expression, then module is being referenced as an object. let tsIdentParent: ts.Node = tsIdentifier; @@ -1189,16 +1209,16 @@ export class TypeScriptLinter { private handleElementAccessExpression(node: ts.Node) { let tsElementAccessExpr = node as ts.ElementAccessExpression; - let tsElemAccessBaseExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.expression); + let tsElemAccessBaseExprType = this.tsTypeChecker.getTypeAtLocation(tsElementAccessExpr.expression); if ( - (tsElemAccessBaseExprType.isClassOrInterface() && !Utils.isGenericArrayType(tsElemAccessBaseExprType)) || - Utils.isObjectLiteralType(tsElemAccessBaseExprType) || Utils.isEnumType(tsElemAccessBaseExprType) || - Utils.isThisOrSuperExpr(tsElementAccessExpr.expression) + (tsElemAccessBaseExprType.isClassOrInterface() && !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType)) || + this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || this.tsUtils.isEnumType(tsElemAccessBaseExprType) || + this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) ) { let autofix = Autofixer.fixPropertyAccessByIndex(node); let autofixable = autofix != undefined; - if (!Autofixer.shouldAutofix(node, FaultID.PropertyAccessByIndex)) + if (!this.autofixesInfo.shouldAutofix(node, FaultID.PropertyAccessByIndex)) autofix = undefined; this.incrementCounters(node, FaultID.PropertyAccessByIndex, autofixable, autofix); @@ -1207,17 +1227,17 @@ export class TypeScriptLinter { private handleEnumMember(node: ts.Node) { let tsEnumMember = node as ts.EnumMember; - let tsEnumMemberType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsEnumMember); - let constVal = TypeScriptLinter.tsTypeChecker.getConstantValue(tsEnumMember); + let tsEnumMemberType = this.tsTypeChecker.getTypeAtLocation(tsEnumMember); + let constVal = this.tsTypeChecker.getConstantValue(tsEnumMember); - if (tsEnumMember.initializer && !Utils.isValidEnumMemberInit(tsEnumMember.initializer)) + if (tsEnumMember.initializer && !this.tsUtils.isValidEnumMemberInit(tsEnumMember.initializer)) this.incrementCounters(node, FaultID.EnumMemberNonConstInit); // check for type - all members should be of same type let enumDecl = tsEnumMember.parent; let firstEnumMember = enumDecl.members[0]; - let firstEnumMemberType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(firstEnumMember); - let firstElewmVal = TypeScriptLinter.tsTypeChecker.getConstantValue(firstEnumMember); + let firstEnumMemberType = this.tsTypeChecker.getTypeAtLocation(firstEnumMember); + let firstElewmVal = this.tsTypeChecker.getConstantValue(firstEnumMember); // each string enum member has its own type // so check that value type is string if( constVal !==undefined && typeof constVal === 'string' && @@ -1233,16 +1253,15 @@ export class TypeScriptLinter { private handleExportDeclaration(node: ts.Node) { let tsExportDecl = node as ts.ExportDeclaration; - if (tsExportDecl.isTypeOnly) - this.incrementCounters(node, FaultID.TypeOnlyExport); - if (tsExportDecl.moduleSpecifier) - this.incrementCounters(node, FaultID.ReExporting); - } + if (tsExportDecl.isTypeOnly) { + let autofix: Autofix[] | undefined; + if (this.autofixesInfo.shouldAutofix(node, FaultID.TypeOnlyExport)) + autofix = [ Autofixer.dropTypeOnlyFlag(tsExportDecl) ]; + this.incrementCounters(node, FaultID.TypeOnlyExport, true, autofix); + } - private handleExportSpecifier(node: ts.Node) { - let tsExportSpecifier = node as ts.ExportSpecifier; - if (tsExportSpecifier.propertyName) - this.incrementCounters(node, FaultID.ExportRenaming); + if (tsExportDecl.moduleSpecifier && !tsExportDecl.exportClause) + this.incrementCounters(node, FaultID.LimitedReExporting); } private handleExportAssignment(node: ts.Node) { @@ -1287,18 +1306,18 @@ export class TypeScriptLinter { tsCallExpr.expression.text === 'require' && ts.isVariableDeclaration(tsCallExpr.parent) ) { - let tsType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression); - if (Utils.isInterfaceType(tsType) && tsType.symbol.name === 'NodeRequire') + let tsType = this.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression); + if (this.tsUtils.isInterfaceType(tsType) && tsType.symbol.name === 'NodeRequire') this.incrementCounters(tsCallExpr.parent, FaultID.ImportAssignment); } } private handleGenericCallWithNoTypeArgs(callLikeExpr: ts.CallExpression | ts.NewExpression) { - let callSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(callLikeExpr); + let callSignature = this.tsTypeChecker.getResolvedSignature(callLikeExpr); if (!callSignature) return; let tsSyntaxKind = ts.isNewExpression(callLikeExpr) ? ts.SyntaxKind.Constructor : ts.SyntaxKind.FunctionDeclaration; - let signDecl = TypeScriptLinter.tsTypeChecker.signatureToSignatureDeclaration(callSignature, tsSyntaxKind, + let signDecl = this.tsTypeChecker.signatureToSignatureDeclaration(callSignature, tsSyntaxKind, undefined, ts.NodeBuilderFlags.WriteTypeArgumentsOfSignature | ts.NodeBuilderFlags.IgnoreErrors); if (signDecl?.typeArguments) { @@ -1306,7 +1325,7 @@ export class TypeScriptLinter { let startTypeArg = callLikeExpr.typeArguments?.length ?? 0; for (let i = startTypeArg; i < resolvedTypeArgs.length; ++i) { - if (!Utils.isSupportedType(resolvedTypeArgs[i])) { + if (!this.tsUtils.isSupportedType(resolvedTypeArgs[i])) { this.incrementCounters(callLikeExpr, FaultID.GenericCallNoTypeArgs); break; } @@ -1320,19 +1339,19 @@ export class TypeScriptLinter { ts.isPropertyAccessExpression(tsExpr) && (tsExpr.name.text === 'apply' || tsExpr.name.text === 'bind' || tsExpr.name.text === 'call') ) { - const tsSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsExpr.expression); - if (Utils.isFunctionOrMethod(tsSymbol)) + const tsSymbol = this.tsTypeChecker.getSymbolAtLocation(tsExpr.expression); + if (this.tsUtils.isFunctionOrMethod(tsSymbol)) this.incrementCounters(tsCallExpr, FaultID.FunctionApplyBindCall); } } private handleStructIdentAndUndefinedInArgs(tsCallOrNewExpr: ts.CallExpression | ts.NewExpression) { - let tsSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(tsCallOrNewExpr); + let tsSignature = this.tsTypeChecker.getResolvedSignature(tsCallOrNewExpr); if (!tsSignature || !tsCallOrNewExpr.arguments) return; for (let argIndex = 0; argIndex < tsCallOrNewExpr.arguments.length; ++argIndex) { let tsArg = tsCallOrNewExpr.arguments[argIndex]; - let tsArgType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsArg); + let tsArgType = this.tsTypeChecker.getTypeAtLocation(tsArg); if (!tsArgType) continue; let paramIndex = argIndex < tsSignature.parameters.length ? argIndex : tsSignature.parameters.length-1; @@ -1341,44 +1360,39 @@ export class TypeScriptLinter { let tsParamDecl = tsParamSym.valueDeclaration; if (tsParamDecl && ts.isParameter(tsParamDecl)) { - let tsParamType = TypeScriptLinter.tsTypeChecker.getTypeOfSymbolAtLocation(tsParamSym, tsParamDecl); - if (tsParamDecl.dotDotDotToken && Utils.isGenericArrayType(tsParamType) && tsParamType.typeArguments) + let tsParamType = this.tsTypeChecker.getTypeOfSymbolAtLocation(tsParamSym, tsParamDecl); + if (tsParamDecl.dotDotDotToken && this.tsUtils.isGenericArrayType(tsParamType) && tsParamType.typeArguments) tsParamType = tsParamType.typeArguments[0]; if (!tsParamType) continue; if ( tsArgType.isClassOrInterface() && tsParamType.isClassOrInterface() && - !Utils.relatedByInheritanceOrIdentical(tsArgType, tsParamType) + !this.tsUtils.relatedByInheritanceOrIdentical(tsArgType, tsParamType) ) this.incrementCounters(tsArg, FaultID.StructuralIdentity); - if ( - ts.isIdentifier(tsArg) && tsArg.originalKeywordKind === ts.SyntaxKind.UndefinedKeyword && - (!tsParamType.isUnion() || Utils.isUnsupportedUnionType(tsParamType)) - ) - this.incrementCounters(tsArg, FaultID.UndefinedValue); } } } private handleStdlibAPICall(callExpr: ts.CallExpression) { - let callSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(callExpr); + let callSignature = this.tsTypeChecker.getResolvedSignature(callExpr); if (!callSignature) return; - let sym = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(callExpr.expression); + let sym = this.tsTypeChecker.getSymbolAtLocation(callExpr.expression); if (sym) { let name = sym.getName(); if ( - (Utils.isGlobalSymbol(sym) && Utils.LIMITED_STD_GLOBAL_FUNC.includes(name)) || - (Utils.isStdObjectAPI(sym) && Utils.LIMITED_STD_OBJECT_API.includes(name)) || - (Utils.isStdReflectAPI(sym) && Utils.LIMITED_STD_REFLECT_API.includes(name)) || - (Utils.isStdProxyHandlerAPI(sym) && Utils.LIMITED_STD_PROXYHANDLER_API.includes(name)) || - (Utils.isStdArrayAPI(sym) && Utils.LIMITED_STD_ARRAY_API.includes(name)) || - (Utils.isStdArrayBufferAPI(sym) && Utils.LIMITED_STD_ARRAYBUFFER_API.includes(name)) + (this.tsUtils.isGlobalSymbol(sym) && TsUtils.LIMITED_STD_GLOBAL_FUNC.includes(name)) || + (this.tsUtils.isStdObjectAPI(sym) && TsUtils.LIMITED_STD_OBJECT_API.includes(name)) || + (this.tsUtils.isStdReflectAPI(sym) && TsUtils.LIMITED_STD_REFLECT_API.includes(name)) || + (this.tsUtils.isStdProxyHandlerAPI(sym) && TsUtils.LIMITED_STD_PROXYHANDLER_API.includes(name)) || + (this.tsUtils.isStdArrayAPI(sym) && TsUtils.LIMITED_STD_ARRAY_API.includes(name)) || + (this.tsUtils.isStdArrayBufferAPI(sym) && TsUtils.LIMITED_STD_ARRAYBUFFER_API.includes(name)) ) { this.incrementCounters(callExpr, FaultID.LimitedStdLibApi); } - if( Utils.isSymbolAPI(sym)) { + if( this.tsUtils.isSymbolAPI(sym)) { this.incrementCounters(callExpr, FaultID.SymbolType); } } @@ -1394,21 +1408,36 @@ export class TypeScriptLinter { let tsAsExpr = node as ts.AsExpression; if (tsAsExpr.type.getText() === 'const') this.incrementCounters(node, FaultID.ConstAssertion); - let targetType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsAsExpr.type); - let exprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(tsAsExpr.expression); + let targetType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.type); + let exprType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.expression); if ( targetType.isClassOrInterface() && exprType.isClassOrInterface() && - !Utils.relatedByInheritanceOrIdentical(exprType, targetType) && - !Utils.relatedByInheritanceOrIdentical(targetType, exprType) + !this.tsUtils.relatedByInheritanceOrIdentical(exprType, targetType) && + !this.tsUtils.relatedByInheritanceOrIdentical(targetType, exprType) ) this.incrementCounters(tsAsExpr, FaultID.StructuralIdentity); + // check for rule#65: 'number as Number' and 'boolean as Boolean' are disabled + if( + (this.tsUtils.isNumberType(exprType) && targetType.getSymbol()?.getName() === 'Number') || + (this.tsUtils.isBooleanType(exprType) && targetType.getSymbol()?.getName() === 'Boolean') + ) + this.incrementCounters(node, FaultID.TypeAssertion); } private handleTypeReference(node: ts.Node) { let typeRef = node as ts.TypeReferenceNode; - if (ts.isIdentifier(typeRef.typeName) && LinterConfig.standardUtilityTypes.has(typeRef.typeName.text)) + if (ts.isIdentifier(typeRef.typeName) && TsUtils.LIMITED_STANDARD_UTILITY_TYPES.includes(typeRef.typeName.text)) this.incrementCounters(node, FaultID.UtilityType); + else if ( + ts.isIdentifier(typeRef.typeName) && typeRef.typeName.text === 'Partial' && + typeRef.typeArguments && typeRef.typeArguments.length === 1 + ) { + // Using Partial type is allowed only when its argument type is either Class or Interface. + let argType = this.tsTypeChecker.getTypeFromTypeNode(typeRef.typeArguments[0]); + if (!argType || !argType.isClassOrInterface()) + this.incrementCounters(node, FaultID.UtilityType); + } } private handleMetaProperty(node: ts.Node) { @@ -1424,34 +1453,15 @@ export class TypeScriptLinter { }); } - private handleBigIntLiteral(node: ts.Node) { - let autofixes: Autofix[] | undefined; - - if (Autofixer.shouldAutofix(node, FaultID.BigIntLiteral)) { - // If decimal value of literal exceeds 15 digits, create - // BigInt ctor call with string argument. - let isStringArg = false; - let type = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(node); - if ((type.flags & ts.TypeFlags.BigIntLiteral) !== 0) { - let bigIntType = type as ts.BigIntLiteralType; - isStringArg = bigIntType.value.base10Value.length > 15; - } - - autofixes = Autofixer.fixBigIntLiteral(node as ts.BigIntLiteral, isStringArg); - } - - this.incrementCounters(node, FaultID.BigIntLiteral, true, autofixes); - } - private handleSpreadOp(node: ts.Node) { // spread assignment is disabled // spread element is allowed only for arrays as rest parameer if( ts.isSpreadElement(node) ) { let spreadElemNode = node as ts.SpreadElement; - let spreadExprType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(spreadElemNode.expression); + let spreadExprType = this.tsTypeChecker.getTypeAtLocation(spreadElemNode.expression); if (spreadExprType) { - const spreadExprTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(spreadExprType, undefined, ts.NodeBuilderFlags.None); - if (spreadExprTypeNode && Utils.isArrayNotTupleType(spreadExprTypeNode) && ts.isCallLikeExpression(node.parent)) { + const spreadExprTypeNode = this.tsTypeChecker.typeToTypeNode(spreadExprType, undefined, ts.NodeBuilderFlags.None); + if (spreadExprTypeNode && this.tsUtils.isArrayNotTupleType(spreadExprTypeNode) && ts.isCallLikeExpression(node.parent)) { // rest parameter should be the last and the one return; } @@ -1469,36 +1479,66 @@ export class TypeScriptLinter { } } - private handleConstructorDeclaration(node: ts.Node) { - let ctorDecl = node as ts.ConstructorDeclaration; + private handleComments(node: ts.Node) { + // Note: Same comment may be owned by several nodes if their + // start/end position matches. Thus, look for the most parental + // owner of the specific comment (by the node's position). + const srcText = node.getSourceFile().getFullText(); - if (ctorDecl.parameters.some(x => Utils.hasAccessModifier(x))) { - let autofixable = true; - let autofix: Autofix[] | undefined; - let paramTypes: ts.TypeNode[] = []; - - for (const param of ctorDecl.parameters) { - let paramTypeNode = param.type; - if (!paramTypeNode) { - let paramType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(param); - paramTypeNode = TypeScriptLinter.tsTypeChecker.typeToTypeNode(paramType, param, ts.NodeBuilderFlags.None); - if (!paramTypeNode || !Utils.isSupportedType(paramTypeNode)) { - autofixable = false; - break; - } + const parent = node.parent; + if (!parent || parent.getFullStart() !== node.getFullStart()) { + let leadingComments = ts.getLeadingCommentRanges(srcText, node.getFullStart()); + if (leadingComments) { + for (const comment of leadingComments) { + this.checkErrorSuppressingAnnotation(comment, srcText); } - paramTypes.push(paramTypeNode); } + } - if (autofixable && Autofixer.shouldAutofix(node, FaultID.ParameterProperties)) { - autofix = Autofixer.fixCtorParameterProperties(ctorDecl, paramTypes) + if (!parent || parent.getEnd() !== node.getEnd()) { + let trailingComments = ts.getTrailingCommentRanges(srcText, node.getEnd()); + if (trailingComments) { + for (const comment of trailingComments) { + this.checkErrorSuppressingAnnotation(comment, srcText); + } } + } + } + + private checkErrorSuppressingAnnotation(comment: ts.CommentRange, srcText: string) { + const commentContent = comment.kind === ts.SyntaxKind.MultiLineCommentTrivia + ? srcText.slice(comment.pos + 2, comment.end - 2) + : srcText.slice(comment.pos + 2, comment.end); + + if (commentContent.includes('@ts-ignore') || commentContent.includes('@ts-nocheck')) + this.incrementCounters(comment, FaultID.ErrorSuppression); + } - this.incrementCounters(node, FaultID.ParameterProperties, autofixable, autofix); + private handleDecorators(decorators: readonly ts.Decorator[] | undefined): void { + if (!decorators) return; + + for (const decorator of decorators) { + let decoratorName = ''; + if (ts.isIdentifier(decorator.expression)) + decoratorName = decorator.expression.text; + else if (ts.isCallExpression(decorator.expression) && ts.isIdentifier(decorator.expression.expression)) + decoratorName = decorator.expression.expression.text; + + if (!TsUtils.ARKUI_DECORATORS.includes(decoratorName)) + this.incrementCounters(decorator, FaultID.UnsupportedDecorators); } } - public lint(): void { + private handleGetAccessor(node: ts.Node) { + this.handleDecorators((node as ts.GetAccessorDeclaration).decorators); + } + + private handleSetAccessor(node: ts.Node) { + this.handleDecorators((node as ts.SetAccessorDeclaration).decorators); + } + + public lint(sourceFile: ts.SourceFile) { + this.sourceFile = sourceFile; this.visitTSNode(this.sourceFile); } } diff --git a/linter-4.2/src/TypeScriptLinterConfig.ts b/linter-4.2/src/TypeScriptLinterConfig.ts index 761ce0d6a9d39a84217b5a3d6c68ca3bf7f2b845..50b69567a50532cfcd416eb5eeecbd9999f0aae8 100644 --- a/linter-4.2/src/TypeScriptLinterConfig.ts +++ b/linter-4.2/src/TypeScriptLinterConfig.ts @@ -19,11 +19,20 @@ import { FaultID } from './Problems'; export class LinterConfig { static nodeDesc: string[] = []; + // The SyntaxKind enum defines additional elements at the end of the enum + // that serve as markers (FirstX/LastX). Those elements are initialized + // with indices of the previously defined elements. As result, the enum + // may return incorrect name for a certain kind index (e.g. 'FirstStatement' + // instead of 'VariableStatement'). + // The following code creates a map with correct syntax kind names. + // It can be used when need to print name of syntax kind of certain + // AST node in diagnostic messages. + static tsSyntaxKindNames: string[] = []; + // Use static init method, as TypeScript 4.2 doesn't support static blocks. static initStatic() { // Set the feature descriptions (for the output). LinterConfig.nodeDesc[FaultID.AnyType] = '"any" type'; - LinterConfig.nodeDesc[FaultID.UndefinedValue] = '"undefined" value in wrong context'; LinterConfig.nodeDesc[FaultID.SymbolType] = '"symbol" type'; LinterConfig.nodeDesc[FaultID.TupleType] = 'Tuple type'; LinterConfig.nodeDesc[FaultID.ObjectLiteralNoContextType] = 'Object literals with no context Class or Interface type'; @@ -51,7 +60,7 @@ export class LinterConfig { LinterConfig.nodeDesc[FaultID.IntersectionType] = 'intersection types and type literals'; LinterConfig.nodeDesc[FaultID.ObjectTypeLiteral] = 'Object type literals'; LinterConfig.nodeDesc[FaultID.AddWithWrongType] = 'binary "+" with wrong operand'; - LinterConfig.nodeDesc[FaultID.BitOpWithWrongType] = 'bit operation with wrong operand'; + // LinterConfig.nodeDesc[FaultID.BitOpWithWrongType] = 'bit operation with wrong operand'; LinterConfig.nodeDesc[FaultID.CommaOperator] = 'comma operator'; LinterConfig.nodeDesc[FaultID.LimitedReturnTypeInference] = 'Functions with limited return type inference'; LinterConfig.nodeDesc[FaultID.ArrowFunctionWithOmittedTypes] = 'Arrow functions with omitted parameter types'; @@ -64,7 +73,6 @@ export class LinterConfig { LinterConfig.nodeDesc[FaultID.CatchWithUnsupportedType] = '"catch" clause with unsupported exception type'; LinterConfig.nodeDesc[FaultID.DeleteOperator] = '"delete" operations'; LinterConfig.nodeDesc[FaultID.DeclWithDuplicateName] = 'Declarations with duplicate name'; - LinterConfig.nodeDesc[FaultID.FuncOptionalParams] = 'Optional parameters in function'; LinterConfig.nodeDesc[FaultID.UnaryArithmNotNumber] = 'Unary arithmetics with not-numeric values'; LinterConfig.nodeDesc[FaultID.ConstructorType] = 'Constructor type'; LinterConfig.nodeDesc[FaultID.CallSignature] = 'Call signatures'; @@ -91,14 +99,11 @@ export class LinterConfig { LinterConfig.nodeDesc[FaultID.TypeOnlyImport] = 'Type-only imports'; LinterConfig.nodeDesc[FaultID.TypeOnlyExport] = 'Type-only exports'; LinterConfig.nodeDesc[FaultID.DefaultImport] = 'Default import declarations'; - LinterConfig.nodeDesc[FaultID.ExportRenaming] = 'Renaming in export declarations'; - LinterConfig.nodeDesc[FaultID.ExportListDeclaration] = 'Export list declarations'; - LinterConfig.nodeDesc[FaultID.ReExporting] = 'Re-exporting declarations'; + LinterConfig.nodeDesc[FaultID.LimitedReExporting] = 'Limited re-exporting declarations'; LinterConfig.nodeDesc[FaultID.ExportAssignment] = 'Export assignments (export = ..)'; LinterConfig.nodeDesc[FaultID.ImportAssignment] = 'Import assignments (import = ..)'; LinterConfig.nodeDesc[FaultID.PropertyRuntimeCheck] = 'Property-based runtime checks'; LinterConfig.nodeDesc[FaultID.GenericCallNoTypeArgs] = 'Generic calls without type arguments'; - LinterConfig.nodeDesc[FaultID.InterfaceOptionalProp] = 'Interface optional property'; LinterConfig.nodeDesc[FaultID.ParameterProperties] = 'Parameter properties in constructor'; LinterConfig.nodeDesc[FaultID.InstanceofUnsupported] = 'Left-hand side of "instanceof" is wrong'; LinterConfig.nodeDesc[FaultID.ShorthandAmbientModuleDecl] = 'Shorthand ambient module declaration'; @@ -117,17 +122,27 @@ export class LinterConfig { LinterConfig.nodeDesc[FaultID.ReadonlyArr] = '"readonly" array or tuple'; LinterConfig.nodeDesc[FaultID.ConstAssertion] = '"as const" assertion'; LinterConfig.nodeDesc[FaultID.ImportAssertion] = 'Import assertion'; - LinterConfig.nodeDesc[FaultID.BigIntLiteral] = 'BigInt literal'; LinterConfig.nodeDesc[FaultID.SpreadOperator] = 'Spread operation'; LinterConfig.nodeDesc[FaultID.LimitedStdLibApi] = 'Limited standard library API'; + LinterConfig.nodeDesc[FaultID.ErrorSuppression] = 'Error suppression annotation'; + LinterConfig.nodeDesc[FaultID.StrictDiagnostic] = 'Strict diagnostic'; + LinterConfig.nodeDesc[FaultID.UnsupportedDecorators] = 'Unsupported decorators'; + + LinterConfig.initTsSyntaxKindNames(); } - // currently utility types from TypeScript extensions are not supported - static standardUtilityTypes: Set = new Set([ - 'Awaited', 'Partial', 'Required', 'Readonly', 'Record', 'Pick', 'Omit', 'Exclude', 'Extract', 'NonNullable', - 'Parameters', 'ConstructorParameters', 'ReturnType', 'InstanceType', 'ThisParameterType', 'OmitThisParameter', - 'ThisType', 'Uppercase', 'Lowercase', 'Capitalize', 'Uncapitalize', - ]); + private static initTsSyntaxKindNames(): void { + const keys = Object.keys(ts.SyntaxKind); + const values = Object.values(ts.SyntaxKind); + + for (let i = 0; i < values.length; i++) { + const val = values[i]; + const kindNum = typeof val === 'string' ? parseInt(val) : val; + if (kindNum && !LinterConfig.tsSyntaxKindNames[kindNum]) { + LinterConfig.tsSyntaxKindNames[kindNum] = keys[i]; + } + } + } // must detect terminals during parsing static terminalTokens: Set = new Set([ @@ -173,7 +188,6 @@ export class LinterConfig { [ts.SyntaxKind.PrivateIdentifier, FaultID.PrivateIdentifier], [ts.SyntaxKind.ConditionalType, FaultID.ConditionalType], [ts.SyntaxKind.MappedType, FaultID.MappedType], [ts.SyntaxKind.JsxElement, FaultID.JsxElement], [ts.SyntaxKind.JsxSelfClosingElement, FaultID.JsxElement], - [ts.SyntaxKind.NamedExports, FaultID.ExportListDeclaration], [ts.SyntaxKind.ImportEqualsDeclaration, FaultID.ImportAssignment], [ts.SyntaxKind.NamespaceExportDeclaration, FaultID.UMDModuleDefinition], ]); diff --git a/linter-4.2/src/Utils.ts b/linter-4.2/src/Utils.ts index 4206677b4e2463ed68e94a005052045634f1671f..838c712373c8495bf099c0b6c887defae0c7459a 100644 --- a/linter-4.2/src/Utils.ts +++ b/linter-4.2/src/Utils.ts @@ -14,738 +14,806 @@ */ import * as ts from 'typescript'; -import { ProblemInfo, TypeScriptLinter } from './TypeScriptLinter'; -import { AutofixInfo } from './Common'; - -const statementKinds = [ - ts.SyntaxKind.Block,ts.SyntaxKind.EmptyStatement,ts.SyntaxKind.VariableStatement,ts.SyntaxKind.ExpressionStatement, - ts.SyntaxKind.IfStatement,ts.SyntaxKind.DoStatement,ts.SyntaxKind.WhileStatement,ts.SyntaxKind.ForStatement, - ts.SyntaxKind.ForInStatement,ts.SyntaxKind.ForOfStatement,ts.SyntaxKind.ContinueStatement, - ts.SyntaxKind.BreakStatement,ts.SyntaxKind.ReturnStatement,ts.SyntaxKind.WithStatement, - ts.SyntaxKind.SwitchStatement,ts.SyntaxKind.LabeledStatement,ts.SyntaxKind.ThrowStatement, - ts.SyntaxKind.TryStatement,ts.SyntaxKind.DebuggerStatement, -]; - -export function isStatementKindNode(tsNode: ts.Node): boolean { - return statementKinds.includes(tsNode.kind); -} +import { ProblemInfo } from './ProblemInfo'; +import { AutofixInfo } from './AutofixInfo'; -export function isAssignmentOperator(tsBinOp: ts.BinaryOperatorToken): boolean { - return tsBinOp.kind >= ts.SyntaxKind.FirstAssignment && tsBinOp.kind <= ts.SyntaxKind.LastAssignment; -} +export function logTscDiagnostic(diagnostics: readonly ts.Diagnostic[], log: (message: any, ...args: any[]) => void) { + diagnostics.forEach((diagnostic) => { + let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); -export function isArrayNotTupleType(tsType: ts.TypeNode | undefined): boolean { - if (tsType && tsType.kind && ts.isArrayTypeNode(tsType)) { - // Check that element type is not a union type to filter out tuple types induced by tuple literals. - const tsElemType = unwrapParenthesizedType(tsType.elementType); - return !ts.isUnionTypeNode(tsElemType); - } + if (diagnostic.file && diagnostic.start) { + const { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); + message = `${diagnostic.file.fileName} (${line + 1}, ${character + 1}): ${message}`; + } - return false; + log(message); + }); } -export function isNumberType(tsType: ts.Type): boolean { - if (tsType.isUnion()) { - for (let tsCompType of tsType.types) { - if ((tsCompType.flags & ts.TypeFlags.NumberLike) === 0) return false; - } - return true; - } - return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0; +export function encodeProblemInfo(problem: ProblemInfo): string { + return `${problem.problem}%${problem.start}%${problem.end}`; } -export function isBooleanType(tsType: ts.Type): boolean { - return (tsType.getFlags() & ts.TypeFlags.BooleanLike) !== 0; +export function decodeAutofixInfo(info: string): AutofixInfo { + let infos = info.split('%'); + return { problemID: infos[0], start: Number.parseInt(infos[1]), end: Number.parseInt(infos[2]) }; } -export function isStringType(tsType: ts.Type): boolean { - if (tsType.isUnion()) { - for (let tsCompType of tsType.types) { - if ((tsCompType.flags & ts.TypeFlags.StringLike) === 0) return false; - } - return true; +/** + * @param sourceFile AST of the processed file + * @param nodeStartPos node's start position (`node.getStart()`) + * @param nodeEndPos node's end position (`node.getEnd()`) + * @param nodeStartLine node's start line. NB! line count from 1, in contrast to TSC + * @returns column index of the node's end if node is located on one line, line's end otherwise + */ +export function getNodeOrLineEnd( + sourceFile: ts.SourceFile, + nodeStartPos: number, + nodeEndPos: number, + nodeStartLine: number, +): number { + const pos = sourceFile.getLineAndCharacterOfPosition(nodeEndPos); + // TSC counts lines and columns from zero + return (pos.line + 1) === nodeStartLine + ? pos.character + : sourceFile.getLineEndOfPosition(nodeStartPos); +} + +export function mergeArrayMaps(lhs: Map, rhs: Map): Map { + if (lhs.size == 0) { + return rhs; + } + if (rhs.size == 0) { + return lhs; } - return (tsType.getFlags() & ts.TypeFlags.StringLike) !== 0; -} -export function unwrapParenthesizedType(tsType: ts.TypeNode): ts.TypeNode { - while (ts.isParenthesizedTypeNode(tsType)) { - tsType = tsType.type; + rhs.forEach((values, key) => { + if (values.length != 0) { + if (lhs.has(key)) { + lhs.get(key)!.push(...values); + } else { + lhs.set(key, values); + } + } + }); + return lhs; +} + +export class TsUtils { + static statementKinds = [ + ts.SyntaxKind.Block,ts.SyntaxKind.EmptyStatement,ts.SyntaxKind.VariableStatement,ts.SyntaxKind.ExpressionStatement, + ts.SyntaxKind.IfStatement,ts.SyntaxKind.DoStatement,ts.SyntaxKind.WhileStatement,ts.SyntaxKind.ForStatement, + ts.SyntaxKind.ForInStatement,ts.SyntaxKind.ForOfStatement,ts.SyntaxKind.ContinueStatement, + ts.SyntaxKind.BreakStatement,ts.SyntaxKind.ReturnStatement,ts.SyntaxKind.WithStatement, + ts.SyntaxKind.SwitchStatement,ts.SyntaxKind.LabeledStatement,ts.SyntaxKind.ThrowStatement, + ts.SyntaxKind.TryStatement,ts.SyntaxKind.DebuggerStatement, + ]; + + static LIMITED_STD_GLOBAL_FUNC = [ + 'eval', 'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'encodeURI', 'encodeURIComponent', 'Encode', 'decodeURI', + 'decodeURIComponent', 'Decode', 'escape', 'unescape', 'ParseHexOctet' + ]; + static LIMITED_STD_GLOBAL_VAR = ['Infinity', 'NaN']; + static LIMITED_STD_OBJECT_API = [ + '__proto__', '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__', 'assign', 'create', + 'defineProperties', 'defineProperty', 'entries', 'freeze', 'fromEntries', 'getOwnPropertyDescriptor', + 'getOwnPropertyDescriptors', 'getOwnPropertyNames', 'getOwnPropertySymbols', 'getPrototypeOf', 'hasOwn', + 'hasOwnProperty', 'is', 'isExtensible', 'isFrozen', 'isPrototypeOf', 'isSealed', 'keys', 'preventExtensions', + 'propertyIsEnumerable', 'seal', 'setPrototypeOf', 'values' + ]; + static LIMITED_STD_REFLECT_API = [ + 'apply', 'construct', 'defineProperty', 'deleteProperty', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', + 'has', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf' + ]; + static LIMITED_STD_PROXYHANDLER_API = [ + 'apply', 'construct', 'defineProperty', 'deleteProperty', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', + 'has', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf' + ]; + static LIMITED_STD_ARRAY_API = ['isArray']; + static LIMITED_STD_ARRAYBUFFER_API = ['isView']; + + static NON_INITIALIZABLE_PROPERTY_DECORATORS = ['Link', 'Consume', 'ObjectLink']; + + static PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE = 2564; + + static LIMITED_STANDARD_UTILITY_TYPES = [ + 'Awaited', 'Required', 'Readonly', 'Pick', 'Omit', 'Exclude', 'Extract', 'NonNullable', 'Parameters', + 'ConstructorParameters', 'ReturnType', 'InstanceType', 'ThisParameterType', 'OmitThisParameter', + 'ThisType', 'Uppercase', 'Lowercase', 'Capitalize', 'Uncapitalize', + ]; + + static ARKUI_DECORATORS = [ + 'Builder', 'BuilderParam', 'Component', 'Consume', 'Entry', 'Link', 'LocalStorageLink', 'LocalStorageProp', + 'ObjectLink', 'Observed', 'Prop', 'Provide', 'State', 'StorageLink', 'StorageProp', 'Watch', + ]; + + constructor(private tsTypeChecker: ts.TypeChecker) { } - return tsType; -} -export function findParentIf(asExpr: ts.AsExpression): ts.IfStatement | null { - let node = asExpr.parent; - while (node) { - if (node.kind === ts.SyntaxKind.IfStatement) - return node as ts.IfStatement; + public isStatementKindNode(tsNode: ts.Node): boolean { + return TsUtils.statementKinds.includes(tsNode.kind); + } - node = node.parent; + public isAssignmentOperator(tsBinOp: ts.BinaryOperatorToken): boolean { + return tsBinOp.kind >= ts.SyntaxKind.FirstAssignment && tsBinOp.kind <= ts.SyntaxKind.LastAssignment; } - return null; -} + public isArrayNotTupleType(tsType: ts.TypeNode | undefined): boolean { + if (tsType && tsType.kind && ts.isArrayTypeNode(tsType)) { + // Check that element type is not a union type to filter out tuple types induced by tuple literals. + const tsElemType = this.unwrapParenthesizedType(tsType.elementType); + return !ts.isUnionTypeNode(tsElemType); + } -export function isDestructuringAssignmentLHS( - tsExpr: ts.ArrayLiteralExpression | ts.ObjectLiteralExpression -): boolean { - // Check whether given expression is the LHS part of the destructuring - // assignment (or is a nested element of destructuring pattern). - let tsParent = tsExpr.parent; - let tsCurrentExpr: ts.Node = tsExpr; - while (tsParent) { - if ( - ts.isBinaryExpression(tsParent) && isAssignmentOperator(tsParent.operatorToken) && - tsParent.left === tsCurrentExpr - ) - return true; + return false; + } - if ( - (ts.isForStatement(tsParent) || ts.isForInStatement(tsParent) || ts.isForOfStatement(tsParent)) && - tsParent.initializer && tsParent.initializer === tsCurrentExpr - ) + public isNumberType(tsType: ts.Type): boolean { + if (tsType.isUnion()) { + for (let tsCompType of tsType.types) { + if ((tsCompType.flags & ts.TypeFlags.NumberLike) === 0) return false; + } return true; - - tsCurrentExpr = tsParent; - tsParent = tsParent.parent; + } + return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0; } - return false; -} + public isBooleanType(tsType: ts.Type): boolean { + return (tsType.getFlags() & ts.TypeFlags.BooleanLike) !== 0; + } -export function isEnumType(tsType: ts.Type): boolean { - // Note: For some reason, test (tsType.flags & ts.TypeFlags.Enum) != 0 doesn't work here. - // Must use SymbolFlags to figure out if this is an enum type. - return tsType.symbol && (tsType.symbol.flags & ts.SymbolFlags.Enum) !== 0; -} + public isStringType(tsType: ts.Type): boolean { + if (tsType.isUnion()) { + for (let tsCompType of tsType.types) { + if ((tsCompType.flags & ts.TypeFlags.StringLike) === 0) return false; + } + return true; + } + return (tsType.getFlags() & ts.TypeFlags.StringLike) !== 0; + } -export function isObjectLiteralType(tsType: ts.Type): boolean { - return tsType.symbol && (tsType.symbol.flags & ts.SymbolFlags.ObjectLiteral) !== 0; -} + public unwrapParenthesizedType(tsType: ts.TypeNode): ts.TypeNode { + while (ts.isParenthesizedTypeNode(tsType)) { + tsType = tsType.type; + } + return tsType; + } -export function isNumberLikeType(tsType: ts.Type): boolean { - return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0; -} + public findParentIf(asExpr: ts.AsExpression): ts.IfStatement | null { + let node = asExpr.parent; + while (node) { + if (node.kind === ts.SyntaxKind.IfStatement) + return node as ts.IfStatement; -export function hasModifier(tsModifiers: readonly ts.Modifier[] | undefined, tsModifierKind: number): boolean { - // Sanity check. - if (!tsModifiers) return false; + node = node.parent; + } - for (const tsModifier of tsModifiers) { - if (tsModifier.kind === tsModifierKind) return true; + return null; } - return false; -} - -export function unwrapParenthesized(tsExpr: ts.Expression): ts.Expression { - let unwrappedExpr = tsExpr; - while (ts.isParenthesizedExpression(unwrappedExpr)) - unwrappedExpr = unwrappedExpr.expression; + public isDestructuringAssignmentLHS( + tsExpr: ts.ArrayLiteralExpression | ts.ObjectLiteralExpression + ): boolean { + // Check whether given expression is the LHS part of the destructuring + // assignment (or is a nested element of destructuring pattern). + let tsParent = tsExpr.parent; + let tsCurrentExpr: ts.Node = tsExpr; + while (tsParent) { + if ( + ts.isBinaryExpression(tsParent) && this.isAssignmentOperator(tsParent.operatorToken) && + tsParent.left === tsCurrentExpr + ) + return true; - return unwrappedExpr; -} + if ( + (ts.isForStatement(tsParent) || ts.isForInStatement(tsParent) || ts.isForOfStatement(tsParent)) && + tsParent.initializer && tsParent.initializer === tsCurrentExpr + ) + return true; -export function symbolHasDuplicateName(symbol: ts.Symbol, tsDeclKind: ts.SyntaxKind): boolean { - // Type Checker merges all declarations with the same name in one scope into one symbol. - // Thus, check whether the symbol of certain declaration has any declaration with - // different syntax kind. - const symbolDecls = symbol?.getDeclarations(); - if (symbolDecls) { - for (const symDecl of symbolDecls) { - // Don't count declarations with 'Identifier' syntax kind as those - // usually depict declaring an object's property through assignment. - if (symDecl.kind !== ts.SyntaxKind.Identifier && symDecl.kind !== tsDeclKind) return true; + tsCurrentExpr = tsParent; + tsParent = tsParent.parent; } - } - - return false; -} -export function isReferenceType(tsType: ts.Type): boolean { - const f = tsType.getFlags(); - return ( - (f & ts.TypeFlags.InstantiableNonPrimitive) != 0 || (f & ts.TypeFlags.Object) != 0 || - (f & ts.TypeFlags.Boolean) != 0 || (f & ts.TypeFlags.Enum) != 0 || (f & ts.TypeFlags.NonPrimitive) != 0 || - (f & ts.TypeFlags.Number) != 0 || (f & ts.TypeFlags.String) != 0 - ); -} + return false; + } -export function isPrimitiveType(type: ts.Type): boolean { - const f = type.getFlags(); - return ( - (f & ts.TypeFlags.Boolean) != 0 || (f & ts.TypeFlags.BooleanLiteral) != 0 || - (f & ts.TypeFlags.Number) != 0 || (f & ts.TypeFlags.NumberLiteral) != 0 - // In ArkTS 'string' is not a primitive type. So for the common subset 'string' - // should be considered as a reference type. That is why next line is commented out. - //(f & ts.TypeFlags.String) != 0 || (f & ts.TypeFlags.StringLiteral) != 0 - ); -} + public isEnumType(tsType: ts.Type): boolean { + // Note: For some reason, test (tsType.flags & ts.TypeFlags.Enum) != 0 doesn't work here. + // Must use SymbolFlags to figure out if this is an enum type. + return tsType.symbol && (tsType.symbol.flags & ts.SymbolFlags.Enum) !== 0; + } -export function isTypeSymbol(symbol: ts.Symbol | undefined): boolean { - return ( - !!symbol && !!symbol.flags && - ((symbol.flags & ts.SymbolFlags.Class) !== 0 || (symbol.flags & ts.SymbolFlags.Interface) !== 0) - ); -} + public isEnumMemberType(tsType: ts.Type): boolean { + // Note: For some reason, test (tsType.flags & ts.TypeFlags.Enum) != 0 doesn't work here. + // Must use SymbolFlags to figure out if this is an enum type. + return tsType.symbol && (tsType.symbol.flags & ts.SymbolFlags.EnumMember) !== 0; + } -// Check whether type is generic 'Array' type defined in TypeScript standard library. -export function isGenericArrayType(tsType: ts.Type): tsType is ts.TypeReference { - return ( - isTypeReference(tsType) && tsType.typeArguments?.length === 1 && tsType.target.typeParameters?.length === 1 && - tsType.getSymbol()?.getName() === 'Array' - ); -} + public isObjectLiteralType(tsType: ts.Type): boolean { + return tsType.symbol && (tsType.symbol.flags & ts.SymbolFlags.ObjectLiteral) !== 0; + } -export function isTypeReference(tsType: ts.Type): tsType is ts.TypeReference { - return ( - (tsType.getFlags() & ts.TypeFlags.Object) !== 0 && - ((tsType as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0 - ); -} + public isNumberLikeType(tsType: ts.Type): boolean { + return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0; + } -export function isNullType(tsTypeNode: ts.TypeNode): boolean { - return (ts.isLiteralTypeNode(tsTypeNode) && tsTypeNode.literal.kind === ts.SyntaxKind.NullKeyword); -} + public hasModifier(tsModifiers: readonly ts.Modifier[] | undefined, tsModifierKind: number): boolean { + // Sanity check. + if (!tsModifiers) return false; -export function isThisOrSuperExpr(tsExpr: ts.Expression): boolean { - return (tsExpr.kind == ts.SyntaxKind.ThisKeyword || tsExpr.kind == ts.SyntaxKind.SuperKeyword); -} + for (const tsModifier of tsModifiers) { + if (tsModifier.kind === tsModifierKind) return true; + } -export function isPrototypeSymbol(symbol: ts.Symbol | undefined): boolean { - return (!!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Prototype) !== 0); -} + return false; + } -export function isFunctionSymbol(symbol: ts.Symbol | undefined): boolean { - return (!!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Function) !== 0); -} + public unwrapParenthesized(tsExpr: ts.Expression): ts.Expression { + let unwrappedExpr = tsExpr; + while (ts.isParenthesizedExpression(unwrappedExpr)) + unwrappedExpr = unwrappedExpr.expression; -export function isInterfaceType(tsType: ts.Type | undefined): boolean { - return ( - !!tsType && !!tsType.symbol && !!tsType.symbol.flags && - (tsType.symbol.flags & ts.SymbolFlags.Interface) !== 0 - ); -} + return unwrappedExpr; + } -export function isAnyType(tsType: ts.Type): tsType is ts.TypeReference { - return (tsType.getFlags() & ts.TypeFlags.Any) !== 0; -} + public symbolHasDuplicateName(symbol: ts.Symbol, tsDeclKind: ts.SyntaxKind): boolean { + // Type Checker merges all declarations with the same name in one scope into one symbol. + // Thus, check whether the symbol of certain declaration has any declaration with + // different syntax kind. + const symbolDecls = symbol?.getDeclarations(); + if (symbolDecls) { + for (const symDecl of symbolDecls) { + // Don't count declarations with 'Identifier' syntax kind as those + // usually depict declaring an object's property through assignment. + if (symDecl.kind !== ts.SyntaxKind.Identifier && symDecl.kind !== tsDeclKind) return true; + } + } -export function isUnsupportedType(tsType: ts.Type): boolean { - return ( - !!tsType.flags && ((tsType.flags & ts.TypeFlags.Any) !== 0 || (tsType.flags & ts.TypeFlags.Unknown) !== 0 || - (tsType.flags & ts.TypeFlags.Intersection) !== 0) - ); -} + return false; + } -export function isUnsupportedUnionType(tsType: ts.Type): boolean { - if (tsType.isUnion()) { - return !isNullableUnionType(tsType) && !isBooleanUnionType(tsType); + public isReferenceType(tsType: ts.Type): boolean { + const f = tsType.getFlags(); + return ( + (f & ts.TypeFlags.InstantiableNonPrimitive) != 0 || (f & ts.TypeFlags.Object) != 0 || + (f & ts.TypeFlags.Boolean) != 0 || (f & ts.TypeFlags.Enum) != 0 || (f & ts.TypeFlags.NonPrimitive) != 0 || + (f & ts.TypeFlags.Number) != 0 || (f & ts.TypeFlags.String) != 0 + ); } - return false; -} -function isNullableUnionType(tsUnionType: ts.UnionType): boolean { - let tsTypes = tsUnionType.types; - return ( - tsTypes.length === 2 && - ((tsTypes[0].flags & ts.TypeFlags.Null) !== 0 || (tsTypes[1].flags & ts.TypeFlags.Null) !== 0) - ); -} + public isPrimitiveType(type: ts.Type): boolean { + const f = type.getFlags(); + return ( + (f & ts.TypeFlags.Boolean) != 0 || (f & ts.TypeFlags.BooleanLiteral) != 0 || + (f & ts.TypeFlags.Number) != 0 || (f & ts.TypeFlags.NumberLiteral) != 0 + // In ArkTS 'string' is not a primitive type. So for the common subset 'string' + // should be considered as a reference type. That is why next line is commented out. + //(f & ts.TypeFlags.String) != 0 || (f & ts.TypeFlags.StringLiteral) != 0 + ); + } -function isBooleanUnionType(tsUnionType: ts.UnionType): boolean { - // For some reason, 'boolean' type is also represented as as union - // of 'true' and 'false' literal types. This form of 'union' type - // should be considered as supported. - let tsCompTypes = tsUnionType.types; - return ( - tsUnionType.flags === (ts.TypeFlags.Boolean | ts.TypeFlags.Union) && tsCompTypes.length === 2 && - tsCompTypes[0].flags === ts.TypeFlags.BooleanLiteral && (tsCompTypes[1].flags === ts.TypeFlags.BooleanLiteral) - ); -} + public isTypeSymbol(symbol: ts.Symbol | undefined): boolean { + return ( + !!symbol && !!symbol.flags && + ((symbol.flags & ts.SymbolFlags.Class) !== 0 || (symbol.flags & ts.SymbolFlags.Interface) !== 0) + ); + } -export function isFunctionOrMethod(tsSymbol: ts.Symbol | undefined): boolean { - return ( - !!tsSymbol && - ((tsSymbol.flags & ts.SymbolFlags.Function) !== 0 || (tsSymbol.flags & ts.SymbolFlags.Method) !== 0) - ); -} + // Check whether type is generic 'Array' type defined in TypeScript standard library. + public isGenericArrayType(tsType: ts.Type): tsType is ts.TypeReference { + return ( + this.isTypeReference(tsType) && tsType.typeArguments?.length === 1 && tsType.target.typeParameters?.length === 1 && + tsType.getSymbol()?.getName() === 'Array' + ); + } -export function isMethodAssignment(tsSymbol: ts.Symbol | undefined): boolean { - return ( - !!tsSymbol && - ((tsSymbol.flags & ts.SymbolFlags.Method) !== 0 && (tsSymbol.flags & ts.SymbolFlags.Assignment) !== 0) - ); -} + public isTypeReference(tsType: ts.Type): tsType is ts.TypeReference { + return ( + (tsType.getFlags() & ts.TypeFlags.Object) !== 0 && + ((tsType as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0 + ); + } -function getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | null { - if (tsSymbol && tsSymbol.declarations && tsSymbol.declarations.length > 0) - return tsSymbol.declarations[0]; - return null; -} + public isNullType(tsTypeNode: ts.TypeNode): boolean { + return (ts.isLiteralTypeNode(tsTypeNode) && tsTypeNode.literal.kind === ts.SyntaxKind.NullKeyword); + } -function isVarDeclaration(tsDecl: ts.Declaration): boolean { - return ts.isVariableDeclaration(tsDecl) && ts.isVariableDeclarationList(tsDecl.parent); -} + public isThisOrSuperExpr(tsExpr: ts.Expression): boolean { + return (tsExpr.kind == ts.SyntaxKind.ThisKeyword || tsExpr.kind == ts.SyntaxKind.SuperKeyword); + } -export function isValidEnumMemberInit(tsExpr: ts.Expression): boolean { - if (isIntegerConstantValue(tsExpr.parent as ts.EnumMember)) - return true; - if (isStringConstantValue(tsExpr.parent as ts.EnumMember)) - return true; - return isCompileTimeExpression(tsExpr); -} + public isPrototypeSymbol(symbol: ts.Symbol | undefined): boolean { + return (!!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Prototype) !== 0); + } -export function isCompileTimeExpression(tsExpr: ts.Expression): boolean { - if ( - ts.isParenthesizedExpression(tsExpr) || - (ts.isAsExpression(tsExpr) && tsExpr.type.kind === ts.SyntaxKind.NumberKeyword)) - return isCompileTimeExpression(tsExpr.expression); - - switch (tsExpr.kind) { - case ts.SyntaxKind.PrefixUnaryExpression: - return isPrefixUnaryExprValidEnumMemberInit(tsExpr as ts.PrefixUnaryExpression); - case ts.SyntaxKind.ParenthesizedExpression: - case ts.SyntaxKind.BinaryExpression: - return isBinaryExprValidEnumMemberInit(tsExpr as ts.BinaryExpression); - case ts.SyntaxKind.ConditionalExpression: - return isConditionalExprValidEnumMemberInit(tsExpr as ts.ConditionalExpression); - case ts.SyntaxKind.Identifier: - return isIdentifierValidEnumMemberInit(tsExpr as ts.Identifier); - case ts.SyntaxKind.NumericLiteral: - return isIntegerConstantValue(tsExpr as ts.NumericLiteral); - case ts.SyntaxKind.PropertyAccessExpression: { - // if enum member is in current enum declaration try to get value - // if it comes from another enum consider as constant - const propertyAccess = tsExpr as ts.PropertyAccessExpression; - if(isIntegerConstantValue(propertyAccess)) - return true; - const leftHandSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(propertyAccess.expression); - if( !leftHandSymbol ) - return false; - const decls = leftHandSymbol.getDeclarations(); - if (!decls || decls.length !== 1) - return false; - return ts.isEnumDeclaration(decls[0]); - } - default: - return false; + public isFunctionSymbol(symbol: ts.Symbol | undefined): boolean { + return (!!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Function) !== 0); } -} -function isPrefixUnaryExprValidEnumMemberInit(tsExpr: ts.PrefixUnaryExpression): boolean { - return (isUnaryOpAllowedForEnumMemberInit(tsExpr.operator) && isCompileTimeExpression(tsExpr.operand)); -} + public isInterfaceType(tsType: ts.Type | undefined): boolean { + return ( + !!tsType && !!tsType.symbol && !!tsType.symbol.flags && + (tsType.symbol.flags & ts.SymbolFlags.Interface) !== 0 + ); + } -function isBinaryExprValidEnumMemberInit(tsExpr: ts.BinaryExpression): boolean { - return ( - isBinaryOpAllowedForEnumMemberInit(tsExpr.operatorToken) && isCompileTimeExpression(tsExpr.left) && - isCompileTimeExpression(tsExpr.right) - ); -} + public isAnyType(tsType: ts.Type): tsType is ts.TypeReference { + return (tsType.getFlags() & ts.TypeFlags.Any) !== 0; + } -function isConditionalExprValidEnumMemberInit(tsExpr: ts.ConditionalExpression): boolean { - return (isCompileTimeExpression(tsExpr.whenTrue) && isCompileTimeExpression(tsExpr.whenFalse)); -} + public isUnsupportedType(tsType: ts.Type): boolean { + return ( + !!tsType.flags && ((tsType.flags & ts.TypeFlags.Any) !== 0 || (tsType.flags & ts.TypeFlags.Unknown) !== 0 || + (tsType.flags & ts.TypeFlags.Intersection) !== 0) + ); + } + + public isNullableUnionType(type: ts.Type): boolean { + if (type.isUnion()) { + let unionTypes = type.types; + return ( + unionTypes.length === 2 && + ((unionTypes[0].flags & ts.TypeFlags.Undefined) !== 0 || (unionTypes[1].flags & ts.TypeFlags.Undefined) !== 0) + ); + } -function isIdentifierValidEnumMemberInit(tsExpr: ts.Identifier): boolean { - let tsSymbol = TypeScriptLinter.tsTypeChecker.getSymbolAtLocation(tsExpr); - let tsDecl = getDeclaration(tsSymbol); - return (!!tsDecl && - ( (isVarDeclaration(tsDecl) && isConst(tsDecl.parent)) || - (tsDecl.kind === ts.SyntaxKind.EnumMember) - ) - ); -} + return false; + } -function isUnaryOpAllowedForEnumMemberInit(tsPrefixUnaryOp: ts.PrefixUnaryOperator): boolean { - return ( - tsPrefixUnaryOp === ts.SyntaxKind.PlusToken || tsPrefixUnaryOp === ts.SyntaxKind.MinusToken || - tsPrefixUnaryOp === ts.SyntaxKind.TildeToken - ); -} + public isFunctionOrMethod(tsSymbol: ts.Symbol | undefined): boolean { + return ( + !!tsSymbol && + ((tsSymbol.flags & ts.SymbolFlags.Function) !== 0 || (tsSymbol.flags & ts.SymbolFlags.Method) !== 0) + ); + } -function isBinaryOpAllowedForEnumMemberInit(tsBinaryOp: ts.BinaryOperatorToken): boolean { - return ( - tsBinaryOp.kind === ts.SyntaxKind.AsteriskToken || tsBinaryOp.kind === ts.SyntaxKind.SlashToken || - tsBinaryOp.kind === ts.SyntaxKind.PercentToken || tsBinaryOp.kind === ts.SyntaxKind.MinusToken || - tsBinaryOp.kind === ts.SyntaxKind.PlusToken || tsBinaryOp.kind === ts.SyntaxKind.LessThanLessThanToken || - tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanToken || tsBinaryOp.kind === ts.SyntaxKind.BarBarToken || - tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken || - tsBinaryOp.kind === ts.SyntaxKind.AmpersandToken || tsBinaryOp.kind === ts.SyntaxKind.CaretToken || - tsBinaryOp.kind === ts.SyntaxKind.BarToken || tsBinaryOp.kind === ts.SyntaxKind.AmpersandAmpersandToken - ); -} + public isMethodAssignment(tsSymbol: ts.Symbol | undefined): boolean { + return ( + !!tsSymbol && + ((tsSymbol.flags & ts.SymbolFlags.Method) !== 0 && (tsSymbol.flags & ts.SymbolFlags.Assignment) !== 0) + ); + } -export function isConst(tsNode: ts.Node): boolean { - return !!(ts.getCombinedNodeFlags(tsNode) & ts.NodeFlags.Const); -} + private getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | null { + if (tsSymbol && tsSymbol.declarations && tsSymbol.declarations.length > 0) + return tsSymbol.declarations[0]; + return null; + } -export function isIntegerConstantValue( - tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NumericLiteral -): boolean { - - const tsConstValue = (tsExpr.kind === ts.SyntaxKind.NumericLiteral) ? - Number(tsExpr.getText()) : - TypeScriptLinter.tsTypeChecker.getConstantValue(tsExpr); - return ( - tsConstValue !== undefined && typeof tsConstValue === 'number' && - tsConstValue.toFixed(0) === tsConstValue.toString() - ); -} - -export function isStringConstantValue( - tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression -): boolean { - const tsConstValue = TypeScriptLinter.tsTypeChecker.getConstantValue(tsExpr); - return ( - tsConstValue !== undefined && typeof tsConstValue === 'string' - ); -} + private isVarDeclaration(tsDecl: ts.Declaration): boolean { + return ts.isVariableDeclaration(tsDecl) && ts.isVariableDeclarationList(tsDecl.parent); + } -// Returns true iff typeA is a subtype of typeB -export function relatedByInheritanceOrIdentical(typeA: ts.Type, typeB: ts.Type): boolean { - if (isTypeReference(typeA) && typeA.target !== typeA) typeA = typeA.target; - if (isTypeReference(typeB) && typeB.target !== typeB) typeB = typeB.target; - - if (typeA === typeB || isObjectType(typeB)) return true; - if (!typeA.symbol || !typeA.symbol.declarations) return false; + public isValidEnumMemberInit(tsExpr: ts.Expression): boolean { + if (this.isIntegerConstantValue(tsExpr.parent as ts.EnumMember)) + return true; + if (this.isStringConstantValue(tsExpr.parent as ts.EnumMember)) + return true; + return this.isCompileTimeExpression(tsExpr); + } - for (let typeADecl of typeA.symbol.declarations) { + public isCompileTimeExpression(tsExpr: ts.Expression): boolean { if ( - (!ts.isClassDeclaration(typeADecl) && !ts.isInterfaceDeclaration(typeADecl)) || - !typeADecl.heritageClauses - ) continue; - for (let heritageClause of typeADecl.heritageClauses) { - if (processParentTypes(heritageClause.types, typeB)) return true; + ts.isParenthesizedExpression(tsExpr) || + (ts.isAsExpression(tsExpr) && tsExpr.type.kind === ts.SyntaxKind.NumberKeyword)) + return this.isCompileTimeExpression(tsExpr.expression); + + switch (tsExpr.kind) { + case ts.SyntaxKind.PrefixUnaryExpression: + return this.isPrefixUnaryExprValidEnumMemberInit(tsExpr as ts.PrefixUnaryExpression); + case ts.SyntaxKind.ParenthesizedExpression: + case ts.SyntaxKind.BinaryExpression: + return this.isBinaryExprValidEnumMemberInit(tsExpr as ts.BinaryExpression); + case ts.SyntaxKind.ConditionalExpression: + return this.isConditionalExprValidEnumMemberInit(tsExpr as ts.ConditionalExpression); + case ts.SyntaxKind.Identifier: + return this.isIdentifierValidEnumMemberInit(tsExpr as ts.Identifier); + case ts.SyntaxKind.NumericLiteral: + return this.isIntegerConstantValue(tsExpr as ts.NumericLiteral); + case ts.SyntaxKind.PropertyAccessExpression: { + // if enum member is in current enum declaration try to get value + // if it comes from another enum consider as constant + const propertyAccess = tsExpr as ts.PropertyAccessExpression; + if(this.isIntegerConstantValue(propertyAccess)) + return true; + const leftHandSymbol = this.tsTypeChecker.getSymbolAtLocation(propertyAccess.expression); + if( !leftHandSymbol ) + return false; + const decls = leftHandSymbol.getDeclarations(); + if (!decls || decls.length !== 1) + return false; + return ts.isEnumDeclaration(decls[0]); + } + default: + return false; } } - return false; -} + private isPrefixUnaryExprValidEnumMemberInit(tsExpr: ts.PrefixUnaryExpression): boolean { + return (this.isUnaryOpAllowedForEnumMemberInit(tsExpr.operator) && this.isCompileTimeExpression(tsExpr.operand)); + } -function processParentTypes(parentTypes: ts.NodeArray, typeB: ts.Type): boolean { - for (let baseTypeExpr of parentTypes) { - let baseType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(baseTypeExpr); - if (baseType && relatedByInheritanceOrIdentical(baseType, typeB)) return true; + private isBinaryExprValidEnumMemberInit(tsExpr: ts.BinaryExpression): boolean { + return ( + this.isBinaryOpAllowedForEnumMemberInit(tsExpr.operatorToken) && this.isCompileTimeExpression(tsExpr.left) && + this.isCompileTimeExpression(tsExpr.right) + ); } - return false; -} -export function isObjectType(tsType: ts.Type): boolean { - return ( - tsType && tsType.isClassOrInterface() && tsType.symbol && - (tsType.symbol.name === 'Object' || tsType.symbol.name === 'object') - ); -} + private isConditionalExprValidEnumMemberInit(tsExpr: ts.ConditionalExpression): boolean { + return (this.isCompileTimeExpression(tsExpr.whenTrue) && this.isCompileTimeExpression(tsExpr.whenFalse)); + } -export function logTscDiagnostic(diagnostics: readonly ts.Diagnostic[], log: (message: any, ...args: any[]) => void) { - diagnostics.forEach((diagnostic) => { - let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + private isIdentifierValidEnumMemberInit(tsExpr: ts.Identifier): boolean { + let tsSymbol = this.tsTypeChecker.getSymbolAtLocation(tsExpr); + let tsDecl = this.getDeclaration(tsSymbol); + return (!!tsDecl && + ( (this.isVarDeclaration(tsDecl) && this.isConst(tsDecl.parent)) || + (tsDecl.kind === ts.SyntaxKind.EnumMember) + ) + ); + } - if (diagnostic.file && diagnostic.start) { - const { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start); - message = `${diagnostic.file.fileName} (${line + 1}, ${character + 1}): ${message}`; - } + private isUnaryOpAllowedForEnumMemberInit(tsPrefixUnaryOp: ts.PrefixUnaryOperator): boolean { + return ( + tsPrefixUnaryOp === ts.SyntaxKind.PlusToken || tsPrefixUnaryOp === ts.SyntaxKind.MinusToken || + tsPrefixUnaryOp === ts.SyntaxKind.TildeToken + ); + } - log(message); - }); -} + private isBinaryOpAllowedForEnumMemberInit(tsBinaryOp: ts.BinaryOperatorToken): boolean { + return ( + tsBinaryOp.kind === ts.SyntaxKind.AsteriskToken || tsBinaryOp.kind === ts.SyntaxKind.SlashToken || + tsBinaryOp.kind === ts.SyntaxKind.PercentToken || tsBinaryOp.kind === ts.SyntaxKind.MinusToken || + tsBinaryOp.kind === ts.SyntaxKind.PlusToken || tsBinaryOp.kind === ts.SyntaxKind.LessThanLessThanToken || + tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanToken || tsBinaryOp.kind === ts.SyntaxKind.BarBarToken || + tsBinaryOp.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken || + tsBinaryOp.kind === ts.SyntaxKind.AmpersandToken || tsBinaryOp.kind === ts.SyntaxKind.CaretToken || + tsBinaryOp.kind === ts.SyntaxKind.BarToken || tsBinaryOp.kind === ts.SyntaxKind.AmpersandAmpersandToken + ); + } -export function encodeProblemInfo(problem: ProblemInfo): string { - return `${problem.problem}%${problem.start}%${problem.end}`; -} + public isConst(tsNode: ts.Node): boolean { + return !!(ts.getCombinedNodeFlags(tsNode) & ts.NodeFlags.Const); + } -export function decodeAutofixInfo(info: string): AutofixInfo { - let infos = info.split('%'); - return { problemID: infos[0], start: Number.parseInt(infos[1]), end: Number.parseInt(infos[2]) }; -} + public isIntegerConstantValue( + tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NumericLiteral + ): boolean { + + const tsConstValue = (tsExpr.kind === ts.SyntaxKind.NumericLiteral) ? + Number(tsExpr.getText()) : + this.tsTypeChecker.getConstantValue(tsExpr); + return ( + tsConstValue !== undefined && typeof tsConstValue === 'number' && + tsConstValue.toFixed(0) === tsConstValue.toString() + ); + } + + public isStringConstantValue( + tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression + ): boolean { + const tsConstValue = this.tsTypeChecker.getConstantValue(tsExpr); + return ( + tsConstValue !== undefined && typeof tsConstValue === 'string' + ); + } -export function isCallToFunctionWithOmittedReturnType(tsExpr: ts.Expression): boolean { - if (ts.isCallExpression(tsExpr)) { - let tsCallSignature = TypeScriptLinter.tsTypeChecker.getResolvedSignature(tsExpr); - if (tsCallSignature) { - const tsSignDecl = tsCallSignature.getDeclaration(); - // `tsSignDecl` is undefined when `getResolvedSignature` returns `unknownSignature` - if (!tsSignDecl || !tsSignDecl.type) return true; + // Returns true iff typeA is a subtype of typeB + public relatedByInheritanceOrIdentical(typeA: ts.Type, typeB: ts.Type): boolean { + if (this.isTypeReference(typeA) && typeA.target !== typeA) typeA = typeA.target; + if (this.isTypeReference(typeB) && typeB.target !== typeB) typeB = typeB.target; + + if (typeA === typeB || this.isObjectType(typeB)) return true; + if (!typeA.symbol || !typeA.symbol.declarations) return false; + + for (let typeADecl of typeA.symbol.declarations) { + if ( + (!ts.isClassDeclaration(typeADecl) && !ts.isInterfaceDeclaration(typeADecl)) || + !typeADecl.heritageClauses + ) continue; + for (let heritageClause of typeADecl.heritageClauses) { + if (this.processParentTypes(heritageClause.types, typeB)) return true; + } } - } - return false; -} + return false; + } -function hasReadonlyFields(type: ts.Type): boolean { - if (type.symbol.members === undefined) return false; // No members -> no readonly fields + private processParentTypes(parentTypes: ts.NodeArray, typeB: ts.Type): boolean { + for (let baseTypeExpr of parentTypes) { + let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr); + if (baseType && this.relatedByInheritanceOrIdentical(baseType, typeB)) return true; + } + return false; + } - let result: boolean = false; + public isObjectType(tsType: ts.Type): boolean { + return ( + tsType && tsType.isClassOrInterface() && tsType.symbol && + (tsType.symbol.name === 'Object' || tsType.symbol.name === 'object') + ); + } - type.symbol.members.forEach((value, key) => { - if ( - value.declarations !== undefined && value.declarations.length > 0 && - ts.isPropertyDeclaration(value.declarations[0]) - ) { - let propmMods = value.declarations[0].modifiers; // TSC 4.2 doesn't have 'ts.getModifiers()' method - if (hasModifier(propmMods, ts.SyntaxKind.ReadonlyKeyword)) { - result = true; - return; + public isCallToFunctionWithOmittedReturnType(tsExpr: ts.Expression): boolean { + if (ts.isCallExpression(tsExpr)) { + let tsCallSignature = this.tsTypeChecker.getResolvedSignature(tsExpr); + if (tsCallSignature) { + const tsSignDecl = tsCallSignature.getDeclaration(); + // `tsSignDecl` is undefined when `getResolvedSignature` returns `unknownSignature` + if (!tsSignDecl || !tsSignDecl.type) return true; } } - }); - - return result; -} -function hasDefaultCtor(type: ts.Type): boolean { - if (type.symbol.members === undefined) return true; // No members -> no explicite constructors -> there is default ctor + return false; + } - let hasCtor: boolean = false; // has any constructor - let hasDefaultCtor: boolean = false; // has default constructor + private hasReadonlyFields(type: ts.Type): boolean { + if (type.symbol.members === undefined) return false; // No members -> no readonly fields - type.symbol.members.forEach((value, key) => { - if ((value.flags & ts.SymbolFlags.Constructor) !== 0) { - hasCtor = true; + let result: boolean = false; - if (value.declarations !== undefined && value.declarations.length > 0) { - let declCtor = value.declarations[0] as ts.ConstructorDeclaration; - if (declCtor.parameters.length === 0) { - hasDefaultCtor = true; + type.symbol.members.forEach((value, key) => { + if ( + value.declarations !== undefined && value.declarations.length > 0 && + ts.isPropertyDeclaration(value.declarations[0]) + ) { + let propmMods = value.declarations[0].modifiers; // TSC 4.2 doesn't have 'ts.getModifiers()' method + if (this.hasModifier(propmMods, ts.SyntaxKind.ReadonlyKeyword)) { + result = true; return; } } - } - }); - - return !hasCtor || hasDefaultCtor; // Has no any explicite constructor -> has implicite default constructor. -} + }); -function isAbstractClass(type: ts.Type): boolean { - if (type.isClass() && type.symbol.declarations && type.symbol.declarations.length > 0) { - let declClass = type.symbol.declarations[0] as ts.ClassDeclaration; - let classMods = declClass.modifiers; // TSC 4.2 doesn't have 'ts.getModifiers()' method - if (hasModifier(classMods, ts.SyntaxKind.AbstractKeyword)) - return true; + return result; } - return false; -} + private hasDefaultCtor(type: ts.Type): boolean { + if (type.symbol.members === undefined) return true; // No members -> no explicite constructors -> there is default ctor -export function validateObjectLiteralType(type: ts.Type | undefined): boolean { - return ( - type != undefined && type.isClassOrInterface() && hasDefaultCtor(type) && - !hasReadonlyFields(type) && !isAbstractClass(type) - ); -} + let hasCtor: boolean = false; // has any constructor + let hasDefaultCtor: boolean = false; // has default constructor -export function hasMemberFunction(objectLiteral: ts.ObjectLiteralExpression): boolean { - for (let i = 0; i < objectLiteral.properties.length; i++) { - let prop = objectLiteral.properties[i]; - if (ts.isPropertyAssignment(prop)) { - let propAssignment = prop as ts.PropertyAssignment; - if (ts.isArrowFunction(propAssignment.initializer) || ts.isFunctionExpression(propAssignment.initializer)) { - return true; + type.symbol.members.forEach((value, key) => { + if ((value.flags & ts.SymbolFlags.Constructor) !== 0) { + hasCtor = true; + + if (value.declarations !== undefined && value.declarations.length > 0) { + let declCtor = value.declarations[0] as ts.ConstructorDeclaration; + if (declCtor.parameters.length === 0) { + hasDefaultCtor = true; + return; + } + } } + }); + + return !hasCtor || hasDefaultCtor; // Has no any explicite constructor -> has implicite default constructor. + } + + private isAbstractClass(type: ts.Type): boolean { + if (type.isClass() && type.symbol.declarations && type.symbol.declarations.length > 0) { + let declClass = type.symbol.declarations[0] as ts.ClassDeclaration; + let classMods = declClass.modifiers; // TSC 4.2 doesn't have 'ts.getModifiers()' method + if (this.hasModifier(classMods, ts.SyntaxKind.AbstractKeyword)) + return true; } - }; - return false; -} + return false; + } -function findDelaration(type: ts.ClassDeclaration | ts.InterfaceDeclaration, name: string) - : ts.NamedDeclaration | undefined { - let members: ts.NodeArray | ts.NodeArray = type.members; - let declFound: ts.NamedDeclaration | undefined; + public validateObjectLiteralType(type: ts.Type | undefined): boolean { + return ( + type != undefined && type.isClassOrInterface() && this.hasDefaultCtor(type) && + !this.hasReadonlyFields(type) && !this.isAbstractClass(type) + ); + } - for(let m of members) { - if (m.name && m.name.getText() === name) { - declFound = m; - break; - } + public hasMemberFunction(objectLiteral: ts.ObjectLiteralExpression): boolean { + for (let i = 0; i < objectLiteral.properties.length; i++) { + let prop = objectLiteral.properties[i]; + if (ts.isPropertyAssignment(prop)) { + let propAssignment = prop as ts.PropertyAssignment; + if (ts.isArrowFunction(propAssignment.initializer) || ts.isFunctionExpression(propAssignment.initializer)) { + return true; + } + } + }; + + return false; } - if (declFound || !type.heritageClauses) return declFound; + private findDelaration(type: ts.ClassDeclaration | ts.InterfaceDeclaration, name: string) + : ts.NamedDeclaration | undefined { + let members: ts.NodeArray | ts.NodeArray = type.members; + let declFound: ts.NamedDeclaration | undefined; - // Search in base classes/interfaces - for (let i1 = 0; i1 < type.heritageClauses.length; i1++) { - let v1 = type.heritageClauses[i1]; - for (let i2 = 0; i2 < v1.types.length; i2++) { - let v2 = v1.types[i2]; - let symbol = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(v2.expression).symbol; - if ( - (symbol.flags === ts.SymbolFlags.Class || symbol.flags === ts.SymbolFlags.Interface) && - symbol.declarations && symbol.declarations.length > 0 - ) { - declFound = findDelaration(symbol.declarations[0] as (ts.ClassDeclaration | ts.InterfaceDeclaration), name); + for(let m of members) { + if (m.name && m.name.getText() === name) { + declFound = m; + break; } + } + + if (declFound || !type.heritageClauses) return declFound; + + // Search in base classes/interfaces + for (let i1 = 0; i1 < type.heritageClauses.length; i1++) { + let v1 = type.heritageClauses[i1]; + for (let i2 = 0; i2 < v1.types.length; i2++) { + let v2 = v1.types[i2]; + let symbol = this.tsTypeChecker.getTypeAtLocation(v2.expression).symbol; + if ( + (symbol.flags === ts.SymbolFlags.Class || symbol.flags === ts.SymbolFlags.Interface) && + symbol.declarations && symbol.declarations.length > 0 + ) { + declFound = this.findDelaration(symbol.declarations[0] as (ts.ClassDeclaration | ts.InterfaceDeclaration), name); + } + + if (declFound) break; + }; if (declFound) break; }; - if (declFound) break; - }; + return declFound; + } - return declFound; -} + public areTypesAssignable(lhsType: ts.Type | undefined, rhsExpr: ts.Expression): boolean { + if (lhsType === undefined) return false; + + if (lhsType.isUnion() && ts.isObjectLiteralExpression(rhsExpr)) { + for (let compType of lhsType.types) { + if (this.areTypesAssignable(compType, rhsExpr)) return true; + } + } -export function areTypesAssignable(lhsType: ts.Type | undefined, rhsExpr: ts.Expression): boolean { - if (lhsType === undefined) return false; + // Allow initializing Record objects with object initializer. + if (this.isStdRecordType(lhsType)) return true; + + // For Partial type, validate its argument type. + if (this.isStdPartialType(lhsType)) { + if (lhsType.aliasTypeArguments && lhsType.aliasTypeArguments.length === 1) + lhsType = lhsType.aliasTypeArguments[0]; + else + return false; + } - if (lhsType.isUnion() && ts.isObjectLiteralExpression(rhsExpr)) { - for (let compType of lhsType.types) { - if (areTypesAssignable(compType, rhsExpr)) return true; + if (lhsType.isClassOrInterface()) { + if (!ts.isObjectLiteralExpression(rhsExpr)) return false; + + if (!this.validateObjectLiteralType(lhsType) || this.hasMemberFunction(rhsExpr)) return false; + + return this.validateFields(lhsType, rhsExpr); } - } - if (lhsType.isClassOrInterface()) { - if (!ts.isObjectLiteralExpression(rhsExpr)) return false; + // Always compare the non-nullable variant of types. + lhsType = lhsType.getNonNullableType(); + let rhsType = this.tsTypeChecker.getTypeAtLocation(rhsExpr).getNonNullableType(); - if (!validateObjectLiteralType(lhsType) || hasMemberFunction(rhsExpr)) return false; + if (rhsType.isLiteral() || (rhsType.flags & ts.TypeFlags.BooleanLiteral) !== 0) + rhsType = this.tsTypeChecker.getBaseTypeOfLiteralType(rhsType); - return validateFields(lhsType, rhsExpr); + return lhsType === rhsType; } - let rhsType = TypeScriptLinter.tsTypeChecker.getTypeAtLocation(rhsExpr); - - if (rhsType.isLiteral() || (rhsType.flags & ts.TypeFlags.BooleanLiteral) !== 0) - rhsType = TypeScriptLinter.tsTypeChecker.getBaseTypeOfLiteralType(rhsType); - - return lhsType === rhsType; -} + validateFields(type: ts.Type, objectLiteral: ts.ObjectLiteralExpression): boolean { + if (!type.symbol.declarations) return false; -export function validateFields(type: ts.Type, objectLiteral: ts.ObjectLiteralExpression): boolean { - if (!type.symbol.declarations) return false; + let declType = type.symbol.declarations[0] as (ts.ClassDeclaration | ts.InterfaceDeclaration); - let declType = type.symbol.declarations[0] as (ts.ClassDeclaration | ts.InterfaceDeclaration); + for (let i = 0; i < objectLiteral.properties.length; i++) { + let prop = objectLiteral.properties[i]; + if (ts.isPropertyAssignment(prop)) { + let propAssignment = prop as ts.PropertyAssignment; + let propName = propAssignment.name.getText(); + let decl = this.findDelaration(declType, propName); + if (!decl) { + return false; + } - for (let i = 0; i < objectLiteral.properties.length; i++) { - let prop = objectLiteral.properties[i]; - if (ts.isPropertyAssignment(prop)) { - let propAssignment = prop as ts.PropertyAssignment; - let propName = propAssignment.name.getText(); - let decl = findDelaration(declType, propName); - if (!decl) { - return false; + if (!this.areTypesAssignable(this.tsTypeChecker.getTypeAtLocation(decl), propAssignment.initializer)) { + return false; + } } + }; - if (!areTypesAssignable(TypeScriptLinter.tsTypeChecker.getTypeAtLocation(decl), propAssignment.initializer)) { - return false; - } - } - }; + return true; + } - return true; -} + private isSupportedTypeNodeKind(kind: ts.SyntaxKind): boolean { + return kind !== ts.SyntaxKind.AnyKeyword && kind !== ts.SyntaxKind.UnknownKeyword && + kind !== ts.SyntaxKind.SymbolKeyword && kind !== ts.SyntaxKind.UndefinedKeyword && + kind !== ts.SyntaxKind.ConditionalType && kind !== ts.SyntaxKind.MappedType && + kind !== ts.SyntaxKind.InferType && kind !== ts.SyntaxKind.IndexedAccessType; + } -function isSupportedTypeNodeKind(kind: ts.SyntaxKind): boolean { - return kind !== ts.SyntaxKind.AnyKeyword && kind !== ts.SyntaxKind.UnknownKeyword && - kind !== ts.SyntaxKind.SymbolKeyword && kind !== ts.SyntaxKind.UndefinedKeyword && - kind !== ts.SyntaxKind.ConditionalType && kind !== ts.SyntaxKind.MappedType && - kind !== ts.SyntaxKind.InferType && kind !== ts.SyntaxKind.IndexedAccessType; + public isSupportedType(typeNode: ts.TypeNode): boolean { + if (ts.isParenthesizedTypeNode(typeNode)) return this.isSupportedType(typeNode.type); -} + if (ts.isArrayTypeNode(typeNode)) return this.isSupportedType(typeNode.elementType); -export function isSupportedType(typeNode: ts.TypeNode): boolean { - if (ts.isParenthesizedTypeNode(typeNode)) return isSupportedType(typeNode.type); + if (ts.isTypeReferenceNode(typeNode) && typeNode.typeArguments) { + for (const typeArg of typeNode.typeArguments) + if (!this.isSupportedType(typeArg)) return false; + return true; + } - if (ts.isArrayTypeNode(typeNode)) return isSupportedType(typeNode.elementType); + if (ts.isUnionTypeNode(typeNode)) { + for (const unionTypeElem of typeNode.types) + if (!this.isSupportedType(unionTypeElem)) return false; + return true; + } - if (ts.isTypeReferenceNode(typeNode) && typeNode.typeArguments) { - for (const typeArg of typeNode.typeArguments) - if (!isSupportedType(typeArg)) return false; - return true; + return !ts.isTypeLiteralNode(typeNode) && !ts.isTypeQueryNode(typeNode) && + !ts.isIntersectionTypeNode(typeNode) && !ts.isTupleTypeNode(typeNode) && + this.isSupportedTypeNodeKind(typeNode.kind); } - if (ts.isUnionTypeNode(typeNode)) { - for (const unionTypeElem of typeNode.types) - if (!isSupportedType(unionTypeElem)) return false; - return true; + private getParentSymbolName(symbol: ts.Symbol): string | undefined { + const name = this.tsTypeChecker.getFullyQualifiedName(symbol); + const dotPosition = name.lastIndexOf('.'); + return (dotPosition === -1) ? undefined : name.substring(0, dotPosition); } - return !ts.isTypeLiteralNode(typeNode) && !ts.isTypeQueryNode(typeNode) && - !ts.isIntersectionTypeNode(typeNode) && !ts.isTupleTypeNode(typeNode) && - isSupportedTypeNodeKind(typeNode.kind); -} - -export const LIMITED_STD_GLOBAL_FUNC = [ - 'eval', 'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'encodeURI', 'encodeURIComponent', 'Encode', 'decodeURI', - 'decodeURIComponent', 'Decode', 'escape', 'unescape', 'ParseHexOctet' -]; -export const LIMITED_STD_GLOBAL_VAR = ['Infinity', 'NaN']; -export const LIMITED_STD_OBJECT_API = [ - '__proto__', '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__', 'assign', 'create', - 'defineProperties', 'defineProperty', 'entries', 'freeze', 'fromEntries', 'getOwnPropertyDescriptor', - 'getOwnPropertyDescriptors', 'getOwnPropertyNames', 'getOwnPropertySymbols', 'getPrototypeOf', 'hasOwn', - 'hasOwnProperty', 'is', 'isExtensible', 'isFrozen', 'isPrototypeOf', 'isSealed', 'keys', 'preventExtensions', - 'propertyIsEnumerable', 'seal', 'setPrototypeOf', 'values' -]; -export const LIMITED_STD_REFLECT_API = [ - 'apply', 'construct', 'defineProperty', 'deleteProperty', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', - 'has', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf' -]; -export const LIMITED_STD_PROXYHANDLER_API = [ - 'apply', 'construct', 'defineProperty', 'deleteProperty', 'get', 'getOwnPropertyDescriptor', 'getPrototypeOf', - 'has', 'isExtensible', 'ownKeys', 'preventExtensions', 'set', 'setPrototypeOf' -]; -export const LIMITED_STD_ARRAY_API = ['isArray']; -export const LIMITED_STD_ARRAYBUFFER_API = ['isView']; - -function getParentSymbolName(symbol: ts.Symbol): string | undefined { - const name = TypeScriptLinter.tsTypeChecker.getFullyQualifiedName(symbol); - const dotPosition = name.lastIndexOf('.'); - return (dotPosition === -1) ? undefined : name.substring(0, dotPosition); -} + public isGlobalSymbol(symbol: ts.Symbol): boolean { + let parentName = this.getParentSymbolName(symbol); + return !parentName || parentName === 'global'; + } + public isStdObjectAPI(symbol: ts.Symbol): boolean { + let parentName = this.getParentSymbolName(symbol); + return !!parentName && (parentName === 'Object' || parentName === 'ObjectConstructor'); + } + public isStdReflectAPI(symbol: ts.Symbol): boolean { + let parentName = this.getParentSymbolName(symbol); + return !!parentName && (parentName === 'Reflect'); + } + public isStdProxyHandlerAPI(symbol: ts.Symbol): boolean { + let parentName = this.getParentSymbolName(symbol); + return !!parentName && (parentName === 'ProxyHandler'); + } + public isStdArrayAPI(symbol: ts.Symbol): boolean { + let parentName = this.getParentSymbolName(symbol); + return !!parentName && (parentName === 'Array' || parentName === 'ArrayConstructor'); + } + public isStdArrayBufferAPI(symbol: ts.Symbol): boolean { + let parentName = this.getParentSymbolName(symbol); + return !!parentName && (parentName === 'ArrayBuffer' || parentName === 'ArrayBufferConstructor'); + } -export function isGlobalSymbol(symbol: ts.Symbol): boolean { - let parentName = getParentSymbolName(symbol); - return !parentName || parentName === 'global'; -} -export function isStdObjectAPI(symbol: ts.Symbol): boolean { - let parentName = getParentSymbolName(symbol); - return !!parentName && (parentName === 'Object' || parentName === 'ObjectConstructor'); -} -export function isStdReflectAPI(symbol: ts.Symbol): boolean { - let parentName = getParentSymbolName(symbol); - return !!parentName && (parentName === 'Reflect'); -} -export function isStdProxyHandlerAPI(symbol: ts.Symbol): boolean { - let parentName = getParentSymbolName(symbol); - return !!parentName && (parentName === 'ProxyHandler'); -} -export function isStdArrayAPI(symbol: ts.Symbol): boolean { - let parentName = getParentSymbolName(symbol); - return !!parentName && (parentName === 'Array' || parentName === 'ArrayConstructor'); -} -export function isStdArrayBufferAPI(symbol: ts.Symbol): boolean { - let parentName = getParentSymbolName(symbol); - return !!parentName && (parentName === 'ArrayBuffer' || parentName === 'ArrayBufferConstructor'); -} + public isSymbolAPI(symbol: ts.Symbol): boolean { + let parentName = this.getParentSymbolName(symbol); + if (parentName) + return (parentName === 'Symbol' || parentName === 'SymbolConstructor'); + else + return ( symbol.escapedName === 'Symbol' || symbol.escapedName === 'SymbolConstructor'); + } -export function isSymbolAPI(symbol: ts.Symbol): boolean { - let parentName = getParentSymbolName(symbol); - if( !!parentName ) - return (parentName === 'Symbol' || parentName === 'SymbolConstructor'); - else - return ( symbol.escapedName === 'Symbol' || symbol.escapedName === 'SymbolConstructor'); -} + public isDefaultImport(importSpec: ts.ImportSpecifier): boolean { + return importSpec?.propertyName?.text === 'default'; + } + public getStartPos(nodeOrComment: ts.Node | ts.CommentRange): number { + return (nodeOrComment.kind === ts.SyntaxKind.SingleLineCommentTrivia || nodeOrComment.kind === ts.SyntaxKind.MultiLineCommentTrivia) + ? (nodeOrComment as ts.CommentRange).pos + : (nodeOrComment as ts.Node).getStart(); + } -export function hasAccessModifier(decl: ts.Declaration): boolean { - let modifiers = decl.modifiers; // TSC 4.2 doesn't have 'ts.getModifiers()' method - return ( - !!modifiers && - (hasModifier(modifiers, ts.SyntaxKind.PublicKeyword) || - hasModifier(modifiers, ts.SyntaxKind.ProtectedKeyword) || - hasModifier(modifiers, ts.SyntaxKind.PrivateKeyword)) - ); -} + public getEndPos(nodeOrComment: ts.Node | ts.CommentRange): number { + return (nodeOrComment.kind === ts.SyntaxKind.SingleLineCommentTrivia || nodeOrComment.kind === ts.SyntaxKind.MultiLineCommentTrivia) + ? (nodeOrComment as ts.CommentRange).end + : (nodeOrComment as ts.Node).getEnd(); + } -export function getModifier(modifiers: readonly ts.Modifier[] | undefined, modifierKind: ts.SyntaxKind): ts.Modifier | undefined { - if (!modifiers) return undefined; - return modifiers.find(x => x.kind === modifierKind); + public isStdRecordType(type: ts.Type): boolean { + const sym = type.aliasSymbol; + return !!sym && sym.getName() === 'Record' && this.isGlobalSymbol(sym); + } + + public isStdPartialType(type: ts.Type): boolean { + const sym = type.aliasSymbol; + return !!sym && sym.getName() === 'Partial' && this.isGlobalSymbol(sym); + } } - -export function getAccessModifier(modifiers: readonly ts.Modifier[] | undefined): ts.Modifier | undefined { - return getModifier(modifiers, ts.SyntaxKind.PublicKeyword) ?? - getModifier(modifiers, ts.SyntaxKind.ProtectedKeyword) ?? - getModifier(modifiers, ts.SyntaxKind.PrivateKeyword); -} \ No newline at end of file diff --git a/linter-4.2/src/ts-diagnostics/TSCCompiledProgram.ts b/linter-4.2/src/ts-diagnostics/TSCCompiledProgram.ts new file mode 100644 index 0000000000000000000000000000000000000000..b47515c6029d8e32e47824b768bf8854341fcbb2 --- /dev/null +++ b/linter-4.2/src/ts-diagnostics/TSCCompiledProgram.ts @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from 'typescript'; +import { ProblemInfo } from '../ProblemInfo'; +import { ProblemSeverity } from '../ProblemSeverity'; +import { LintOptions } from '../LintOptions'; +import { TypeScriptDiagnosticsExtractor } from './TypeScriptDiagnosticsExtractor'; +import { compile } from '../CompilerWrapper'; +import { getNodeOrLineEnd } from '../Utils'; +import { FaultID } from '../Problems'; + +export class TSCCompiledProgram { + private diagnosticsExtractor: TypeScriptDiagnosticsExtractor; + private wasStrict: boolean; + + constructor(program: ts.Program, options: LintOptions) { + const { strict, nonStrict, wasStrict } = getTwoCompiledVersions(program, options); + this.diagnosticsExtractor = new TypeScriptDiagnosticsExtractor(strict, nonStrict); + this.wasStrict = wasStrict; + } + + public getOriginalProgram(): ts.Program { + return this.wasStrict + ? this.diagnosticsExtractor.strictProgram + : this.diagnosticsExtractor.nonStrictProgram; + } + + public getStrictDiagnostics(sourceFile: ts.SourceFile): ts.Diagnostic[] { + return this.diagnosticsExtractor.getStrictDiagnostics(sourceFile); + } +} + +export function getStrictOptions(strict: boolean = true) { + return { + strictNullChecks: strict, + strictFunctionTypes: strict, + strictPropertyInitialization: strict, + noImplicitReturns: strict, + } +} + +function getTwoCompiledVersions( + program: ts.Program, + options: LintOptions, +): { strict: ts.Program; nonStrict: ts.Program; wasStrict: boolean } { + const compilerOptions = { ...program.getCompilerOptions()}; + + const wasStrict = inverseStrictOptions(compilerOptions); + const inversedOptions = getStrictOptions(!wasStrict); + const withInversedOptions = compile(options, inversedOptions); + + return { + strict: wasStrict ? program : withInversedOptions, + nonStrict: wasStrict ? withInversedOptions : program, + wasStrict: wasStrict, + } +} + +/** + * Returns true if options were initially strict + */ +function inverseStrictOptions(compilerOptions: ts.CompilerOptions): boolean { + const strictOptions = getStrictOptions(); + let wasStrict = false; + Object.keys(strictOptions).forEach(x => { + wasStrict = wasStrict || !!compilerOptions[x]; + }); + // wasStrict evaluates true if any of the strict options was set + return wasStrict; +} + +export function transformDiagnostic(diagnostic: ts.Diagnostic): ProblemInfo { + const { line, column } = getLineAndColumn(diagnostic); + const startPos = diagnostic.start!; + const endPos = startPos + diagnostic.length!; + // TODO: set correct information & create .md rule description + return { + line: line, + column: column, + endColumn: getNodeOrLineEnd(diagnostic.file!, startPos, endPos, line), + start: startPos, + end: endPos, + type: 'StrictModeError', + severity: ProblemSeverity.ERROR, // expect strict options to always present + problem: FaultID[FaultID.StrictDiagnostic], + suggest: diagnostic.messageText.toString(), + rule: diagnostic.messageText.toString(), + ruleTag: 0, + autofixable: false, + }; +} + +/** + * Returns line and column of the diagnostic's node, counts from 1 + */ +function getLineAndColumn(diagnostic: ts.Diagnostic): { line: number; column: number } { + let { line, character } = diagnostic.file!.getLineAndCharacterOfPosition(diagnostic.start!); + // TSC counts lines and columns from zero + return { + line: line + 1, + column: character + 1, + } +} diff --git a/linter-4.2/src/ts-diagnostics/TypeScriptDiagnosticsExtractor.ts b/linter-4.2/src/ts-diagnostics/TypeScriptDiagnosticsExtractor.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac8df7e1a6832fec87d0574f54a957d7766fd18a --- /dev/null +++ b/linter-4.2/src/ts-diagnostics/TypeScriptDiagnosticsExtractor.ts @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from 'typescript'; + +export class TypeScriptDiagnosticsExtractor { + constructor(public strictProgram: ts.Program, public nonStrictProgram: ts.Program) { + } + + /** + * Returns diagnostics which appear in strict compilation mode only + */ + public getStrictDiagnostics(sourceFile: ts.SourceFile): ts.Diagnostic[] { + const strict = getAllDiagnostics(this.strictProgram, sourceFile); + const nonStrict = getAllDiagnostics(this.nonStrictProgram, sourceFile); + + // collect hashes for later easier comparison + const nonStrictHashes = nonStrict.reduce((result, value) => { + const hash = hashDiagnostic(value); + if (hash) { + result.add(hash); + } + return result; + }, new Set()); + // return diagnostics which weren't detected in non-strict mode + return strict.filter(value => { + const hash = hashDiagnostic(value); + return (hash && !nonStrictHashes.has(hash)); + }); + } +} + +function getAllDiagnostics(program: ts.Program, sourceFile: ts.SourceFile): ts.Diagnostic[] { + return program.getSemanticDiagnostics(sourceFile) + .concat(program.getSyntacticDiagnostics(sourceFile)) + .filter(diag => diag.file === sourceFile); +} + +function hashDiagnostic(diagnostic: ts.Diagnostic): string | undefined { + if (diagnostic.start === undefined || diagnostic.length === undefined) { + return undefined; + } + return `${diagnostic.code}%${diagnostic.start}%${diagnostic.length}`; +} diff --git a/linter-4.2/test/arkui_decorators.ts b/linter-4.2/test/arkui_decorators.ts new file mode 100644 index 0000000000000000000000000000000000000000..129a621a4c4322cdde0f0d3523111e9610a61b4a --- /dev/null +++ b/linter-4.2/test/arkui_decorators.ts @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type PropDecorator = (target: any, propertyKey: any) => void; +type PropDecoratorCall = ((val: string) => PropDecorator); +type PropDecoratorWithCall = PropDecorator & PropDecoratorCall; + +let Builder: PropDecorator; +let BuilderParam: PropDecorator; +let Component: PropDecorator = (x, y) => {}; +let Consume: PropDecoratorWithCall; +let Entry: PropDecoratorWithCall = () => ((x, y) => {}); +let Link: PropDecorator; +let LocalStorageLink: PropDecoratorCall; +let LocalStorageProp: PropDecoratorCall; +let ObjectLink: PropDecorator; +let Observed: PropDecorator = (x, y) => {}; +let Prop: PropDecorator; +let Provide: PropDecoratorWithCall; +let State: PropDecorator; +let StorageLink: PropDecoratorCall; +let StorageProp: PropDecoratorCall; +let Watch: PropDecoratorCall; + +let InvalidDecorator: PropDecorator = (x, y) => {}; +let InvalidDecoratorWithCall: PropDecoratorWithCall = () => ((x, y) => {}); + +@Observed class Person { + name: string; + age: number; +} + +@Component +class PersonView { + @BuilderParam + builderParam: number; + + @Consume + @Consume('1') + consume: string; + + @Link + link: number; + + @LocalStorageLink('2') + @LocalStorageProp('3') + localStorage: string; + + @ObjectLink + objLink: string; + + @Prop + prop: string; + + @Provide + @Provide('4') + provide: number; + + @State + state: number; + + @StorageLink('5') + @StorageProp('6') + storage: string; + + @Watch('7') + watch: number; + + @Builder + foo() {} +} + +@Entry +@Component +class PageEntry { + @InvalidDecorator + invalid: string; + + @Builder + @InvalidDecoratorWithCall('10') + bar() {} +} + +@InvalidDecorator +class C {} \ No newline at end of file diff --git a/linter-4.2/test/arkui_decorators.ts.autofix.json b/linter-4.2/test/arkui_decorators.ts.autofix.json new file mode 100644 index 0000000000000000000000000000000000000000..45aefec15388881afa8df49e814557a88454d7d3 --- /dev/null +++ b/linter-4.2/test/arkui_decorators.ts.autofix.json @@ -0,0 +1,144 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 16, + "column": 31, + "problem": "AnyType", + "autofixable": false + }, + { + "line": 16, + "column": 49, + "problem": "AnyType", + "autofixable": false + }, + { + "line": 18, + "column": 30, + "problem": "IntersectionType", + "autofixable": false + }, + { + "line": 22, + "column": 32, + "problem": "ArrowFunctionWithOmittedTypes", + "autofixable": false + }, + { + "line": 24, + "column": 43, + "problem": "ArrowFunctionWithOmittedTypes", + "autofixable": false + }, + { + "line": 29, + "column": 31, + "problem": "ArrowFunctionWithOmittedTypes", + "autofixable": false + }, + { + "line": 37, + "column": 39, + "problem": "ArrowFunctionWithOmittedTypes", + "autofixable": false + }, + { + "line": 38, + "column": 62, + "problem": "ArrowFunctionWithOmittedTypes", + "autofixable": false + }, + { + "line": 88, + "column": 3, + "problem": "UnsupportedDecorators", + "autofixable": false + }, + { + "line": 92, + "column": 3, + "problem": "UnsupportedDecorators", + "autofixable": false + }, + { + "line": 96, + "column": 1, + "problem": "UnsupportedDecorators", + "autofixable": false + }, + { + "line": 41, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 42, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 48, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 59, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 65, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 69, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 72, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 76, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 79, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 89, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/bigint_literals.ts.strict.json b/linter-4.2/test/arkui_decorators.ts.relax.json similarity index 42% rename from linter-4.2/test/bigint_literals.ts.strict.json rename to linter-4.2/test/arkui_decorators.ts.relax.json index d48a5bc13ca99538aa40599584b97c64da5e8b9a..9a7d4e0fb010e2bcc5acd4eab0571c65a2a7e46b 100644 --- a/linter-4.2/test/bigint_literals.ts.strict.json +++ b/linter-4.2/test/arkui_decorators.ts.relax.json @@ -16,73 +16,83 @@ "nodes": [ { "line": 16, - "column": 9, - "problem": "BigIntLiteral" + "column": 31, + "problem": "AnyType" }, { - "line": 17, - "column": 5, - "problem": "UnaryArithmNotNumber" + "line": 16, + "column": 49, + "problem": "AnyType" }, { - "line": 17, - "column": 6, - "problem": "BigIntLiteral" + "line": 18, + "column": 30, + "problem": "IntersectionType" }, { - "line": 18, - "column": 5, - "problem": "BigIntLiteral" + "line": 88, + "column": 3, + "problem": "UnsupportedDecorators" + }, + { + "line": 92, + "column": 3, + "problem": "UnsupportedDecorators" + }, + { + "line": 96, + "column": 1, + "problem": "UnsupportedDecorators" }, { - "line": 19, - "column": 5, - "problem": "BigIntLiteral" + "line": 41, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 20, - "column": 5, - "problem": "BigIntLiteral" + "line": 42, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 21, - "column": 5, - "problem": "BigIntLiteral" + "line": 48, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 22, - "column": 5, - "problem": "BigIntLiteral" + "line": 59, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 23, - "column": 5, - "problem": "BigIntLiteral" + "line": 65, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 24, - "column": 5, - "problem": "BigIntLiteral" + "line": 69, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 25, - "column": 5, - "problem": "BigIntLiteral" + "line": 72, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 26, - "column": 5, - "problem": "BigIntLiteral" + "line": 76, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 27, - "column": 5, - "problem": "BigIntLiteral" + "line": 79, + "column": 3, + "problem": "StrictDiagnostic" }, { - "line": 28, - "column": 5, - "problem": "BigIntLiteral" + "line": 89, + "column": 3, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/arkui_decorators.ts.strict.json b/linter-4.2/test/arkui_decorators.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..309c6f4f71f0177560f529b3465ab88102392cae --- /dev/null +++ b/linter-4.2/test/arkui_decorators.ts.strict.json @@ -0,0 +1,123 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 16, + "column": 31, + "problem": "AnyType" + }, + { + "line": 16, + "column": 49, + "problem": "AnyType" + }, + { + "line": 18, + "column": 30, + "problem": "IntersectionType" + }, + { + "line": 22, + "column": 32, + "problem": "ArrowFunctionWithOmittedTypes" + }, + { + "line": 24, + "column": 43, + "problem": "ArrowFunctionWithOmittedTypes" + }, + { + "line": 29, + "column": 31, + "problem": "ArrowFunctionWithOmittedTypes" + }, + { + "line": 37, + "column": 39, + "problem": "ArrowFunctionWithOmittedTypes" + }, + { + "line": 38, + "column": 62, + "problem": "ArrowFunctionWithOmittedTypes" + }, + { + "line": 88, + "column": 3, + "problem": "UnsupportedDecorators" + }, + { + "line": 92, + "column": 3, + "problem": "UnsupportedDecorators" + }, + { + "line": 96, + "column": 1, + "problem": "UnsupportedDecorators" + }, + { + "line": 41, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 42, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 48, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 59, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 65, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 69, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 72, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 76, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 79, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 89, + "column": 3, + "problem": "StrictDiagnostic" + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/array_literals.ts.relax.json b/linter-4.2/test/array_literals.ts.relax.json index c026bdf47f20313c48c5169ef785e7536e8d60a6..bd8629495f8736b1f36ed50de157cdf0b40da290 100644 --- a/linter-4.2/test/array_literals.ts.relax.json +++ b/linter-4.2/test/array_literals.ts.relax.json @@ -118,6 +118,21 @@ "line": 126, "column": 30, "problem": "ObjectLiteralNoContextType" + }, + { + "line": 18, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 19, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 20, + "column": 3, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/array_literals.ts.strict.json b/linter-4.2/test/array_literals.ts.strict.json index 71304420b55194e5d6198c24ffe4d9719c939eff..f2aced3ffb50a88b207d2e34a548c2086a88565e 100644 --- a/linter-4.2/test/array_literals.ts.strict.json +++ b/linter-4.2/test/array_literals.ts.strict.json @@ -123,6 +123,21 @@ "line": 126, "column": 30, "problem": "ObjectLiteralNoContextType" + }, + { + "line": 18, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 19, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 20, + "column": 3, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/bigint_literals.ts b/linter-4.2/test/bigint_literals.ts deleted file mode 100644 index 47e7ca893d6cf988563b2e8294791ceb7f3de298..0000000000000000000000000000000000000000 --- a/linter-4.2/test/bigint_literals.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2022-2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -let a = 0n; -a = -10n; -a = 125n; -a = 100_200n; -a = 1_200_300_400_500_600_700_800_900_000_100_200_300_400_500_600n; -a = 0x125n; -a = 0X1F_5A_49n; -a = 0x301D20FA9C500B732AAAAB677631EE0n; -a = 0b101010n; -a = 0B1000_1000_1000_1000_1000n; -a = 0B1010_0101_1010_0101_1111_0000_1010_0101_1010_0101_1111_0000_1010_0101_1010_0101_1111_0000n; -a = 0o1234567n; -a = 0O3214741762451246532427261534n; \ No newline at end of file diff --git a/linter-4.2/test/bigint_literals.ts.autofix.json b/linter-4.2/test/bigint_literals.ts.autofix.json deleted file mode 100644 index 0625218bb6d763e073ca1633d16d8b9111341918..0000000000000000000000000000000000000000 --- a/linter-4.2/test/bigint_literals.ts.autofix.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - "copyright": [ - "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", - "Licensed under the Apache License, Version 2.0 (the 'License');", - "you may not use this file except in compliance with the License.", - "You may obtain a copy of the License at", - "", - "http://www.apache.org/licenses/LICENSE-2.0", - "", - "Unless required by applicable law or agreed to in writing, software", - "distributed under the License is distributed on an 'AS IS' BASIS,", - "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", - "See the License for the specific language governing permissions and", - "limitations under the License." - ], - "nodes": [ - { - "line": 16, - "column": 9, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(0)", - "start": 618, - "end": 620 - } - ] - }, - { - "line": 17, - "column": 5, - "problem": "UnaryArithmNotNumber", - "autofixable": false - }, - { - "line": 17, - "column": 6, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(10)", - "start": 627, - "end": 630 - } - ] - }, - { - "line": 18, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(125)", - "start": 636, - "end": 640 - } - ] - }, - { - "line": 19, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(100_200)", - "start": 646, - "end": 654 - } - ] - }, - { - "line": 20, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt('1200300400500600700800900000100200300400500600')", - "start": 660, - "end": 722 - } - ] - }, - { - "line": 21, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(0x125)", - "start": 728, - "end": 734 - } - ] - }, - { - "line": 22, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(0X1F_5A_49)", - "start": 740, - "end": 751 - } - ] - }, - { - "line": 23, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt('0x301D20FA9C500B732AAAAB677631EE0')", - "start": 757, - "end": 791 - } - ] - }, - { - "line": 24, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(0b101010)", - "start": 797, - "end": 806 - } - ] - }, - { - "line": 25, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(0B1000_1000_1000_1000_1000)", - "start": 812, - "end": 839 - } - ] - }, - { - "line": 26, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt('0B101001011010010111110000101001011010010111110000101001011010010111110000')", - "start": 845, - "end": 937 - } - ] - }, - { - "line": 27, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt(0o1234567)", - "start": 943, - "end": 953 - } - ] - }, - { - "line": 28, - "column": 5, - "problem": "BigIntLiteral", - "autofixable": true, - "autofix": [ - { - "replacementText": "BigInt('0O3214741762451246532427261534')", - "start": 959, - "end": 990 - } - ] - } - ] -} \ No newline at end of file diff --git a/linter-4.2/test/binary_wrong_types.ts b/linter-4.2/test/binary_wrong_types.ts index e62f9d37c094dd856896ef41c76bc42034b75e61..d34dc7b1c92c107c1976cac1b12e8612d05726ca 100644 --- a/linter-4.2/test/binary_wrong_types.ts +++ b/linter-4.2/test/binary_wrong_types.ts @@ -104,3 +104,12 @@ const b42 = 2 | 5.5; const b43 = 5.5 | b42; const b44 = 4 ^ 5.5; const b45 = 5.5 ^ b44; + + +let e1 = Const.PI + Const.E; // OK +let e2 = State.FAULT + State.OK; // OK +let e3 = "State: " + State.FAULT; // OK +let e4 = "Const = " + Const.PI; // OK + +let f1 = Const.PI + State.FAULT; // Error +let f2 = State.OK + Const.E; // Error diff --git a/linter-4.2/test/binary_wrong_types.ts.relax.json b/linter-4.2/test/binary_wrong_types.ts.relax.json index f5197b0ef7ad821b8067eb5ca8156605a15a83f7..85286d415e748397ad9712f22f4a013820520ea2 100644 --- a/linter-4.2/test/binary_wrong_types.ts.relax.json +++ b/linter-4.2/test/binary_wrong_types.ts.relax.json @@ -44,130 +44,20 @@ "column": 3, "problem": "EnumMemberNonConstInit" }, - { - "line": 59, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 60, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 61, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 63, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 64, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 65, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 67, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 68, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 69, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 83, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 84, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 85, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 87, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 88, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 89, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 91, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 92, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 93, - "column": 13, - "problem": "BitOpWithWrongType" - }, { "line": 95, "column": 15, "problem": "CommaOperator" }, { - "line": 101, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 102, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 103, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 104, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 105, - "column": 13, - "problem": "BitOpWithWrongType" + "line": 114, + "column": 10, + "problem": "AddWithWrongType" }, { - "line": 106, - "column": 13, - "problem": "BitOpWithWrongType" + "line": 115, + "column": 10, + "problem": "AddWithWrongType" } ] } \ No newline at end of file diff --git a/linter-4.2/test/binary_wrong_types.ts.strict.json b/linter-4.2/test/binary_wrong_types.ts.strict.json index f5197b0ef7ad821b8067eb5ca8156605a15a83f7..85286d415e748397ad9712f22f4a013820520ea2 100644 --- a/linter-4.2/test/binary_wrong_types.ts.strict.json +++ b/linter-4.2/test/binary_wrong_types.ts.strict.json @@ -44,130 +44,20 @@ "column": 3, "problem": "EnumMemberNonConstInit" }, - { - "line": 59, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 60, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 61, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 63, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 64, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 65, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 67, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 68, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 69, - "column": 12, - "problem": "BitOpWithWrongType" - }, - { - "line": 83, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 84, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 85, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 87, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 88, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 89, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 91, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 92, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 93, - "column": 13, - "problem": "BitOpWithWrongType" - }, { "line": 95, "column": 15, "problem": "CommaOperator" }, { - "line": 101, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 102, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 103, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 104, - "column": 13, - "problem": "BitOpWithWrongType" - }, - { - "line": 105, - "column": 13, - "problem": "BitOpWithWrongType" + "line": 114, + "column": 10, + "problem": "AddWithWrongType" }, { - "line": 106, - "column": 13, - "problem": "BitOpWithWrongType" + "line": 115, + "column": 10, + "problem": "AddWithWrongType" } ] } \ No newline at end of file diff --git a/linter-4.2/test/parameter_properties.ts.autofix.json b/linter-4.2/test/catch_clause.ts.autofix.json similarity index 38% rename from linter-4.2/test/parameter_properties.ts.autofix.json rename to linter-4.2/test/catch_clause.ts.autofix.json index 4b7363a1814f03674450522bde6d7d50c9b6c444..c3db7e9f3ffc54a00fead98d8b52c44295c003e8 100644 --- a/linter-4.2/test/parameter_properties.ts.autofix.json +++ b/linter-4.2/test/catch_clause.ts.autofix.json @@ -1,6 +1,6 @@ { "copyright": [ - "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", "Licensed under the Apache License, Version 2.0 (the 'License');", "you may not use this file except in compliance with the License.", "You may obtain a copy of the License at", @@ -16,62 +16,78 @@ "nodes": [ { "line": 17, - "column": 3, - "problem": "ParameterProperties", + "column": 13, + "problem": "ThrowStatement", "autofixable": true, "autofix": [ { - "start": 622, - "end": 622, - "replacementText": "public readonly x: number;\nprotected y: number;\nprivate z: number;\n" - }, - { - "start": 639, - "end": 664, - "replacementText": "x: number" - }, - { - "start": 670, - "end": 689, - "replacementText": "y: number" - }, - { - "start": 695, - "end": 712, - "replacementText": "z: number" - }, - { - "start": 717, - "end": 719, - "replacementText": "{\r\n this.x = x;\r\n this.y = y;\r\n this.z = z;\r\n}" + "start": 634, + "end": 657, + "replacementText": "new Error(\"Catch with 'any' type\")" } ] }, { - "line": 34, + "line": 18, "column": 3, - "problem": "ParameterProperties", + "problem": "CatchWithUnsupportedType", "autofixable": true, "autofix": [ { - "start": 870, - "end": 870, - "replacementText": "public w: string;\nprivate readonly r: number[];\n" - }, + "start": 668, + "end": 674, + "replacementText": "e" + } + ] + }, + { + "line": 18, + "column": 13, + "problem": "AnyType", + "autofixable": false + }, + { + "line": 23, + "column": 13, + "problem": "ThrowStatement", + "autofixable": true, + "autofix": [ { - "start": 893, - "end": 913, - "replacementText": "w = 'default'" - }, + "start": 723, + "end": 750, + "replacementText": "new Error(\"Catch with 'unknown' type\")" + } + ] + }, + { + "line": 24, + "column": 3, + "problem": "CatchWithUnsupportedType", + "autofixable": true, + "autofix": [ { - "start": 927, - "end": 967, - "replacementText": "r: number[] = [1, 2, 3]" - }, + "start": 761, + "end": 771, + "replacementText": "e" + } + ] + }, + { + "line": 24, + "column": 13, + "problem": "UnknownType", + "autofixable": false + }, + { + "line": 29, + "column": 13, + "problem": "ThrowStatement", + "autofixable": true, + "autofix": [ { - "start": 969, - "end": 1021, - "replacementText": "{\r\n this.w = w;\r\n this.r = r;\r\n console.log(q, this.w, e, this.r, this.f);\r\n}" + "start": 820, + "end": 849, + "replacementText": "new Error('Catch without explicit type')" } ] } diff --git a/linter-4.2/test/conditional_types.ts b/linter-4.2/test/conditional_types.ts index 8d46cc39ace1282a10609e30906fae1806dca79b..af657f55078e6b8f20b432567030de20b916b8f9 100644 --- a/linter-4.2/test/conditional_types.ts +++ b/linter-4.2/test/conditional_types.ts @@ -32,7 +32,7 @@ type NameOrId = T extends number ? IdLabel : NameLabel; function createLabel(idOrName: T): NameOrId { - throw 'unimplemented'; + throw 1; } const a = createLabel('typescript'); // let a: NameLabel const b = createLabel(2.8); // let b: IdLabel diff --git a/linter-4.2/test/conditional_types.ts.autofix.json b/linter-4.2/test/conditional_types.ts.autofix.json new file mode 100644 index 0000000000000000000000000000000000000000..a1b7b4871a774b6fd546af4a5b79256e40cbd328 --- /dev/null +++ b/linter-4.2/test/conditional_types.ts.autofix.json @@ -0,0 +1,103 @@ +{ + "copyright": [ + "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 19, + "column": 1, + "problem": "InterfaceOrEnumMerging", + "autofixable": false + }, + { + "line": 22, + "column": 17, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 23, + "column": 17, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 31, + "column": 44, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 35, + "column": 3, + "problem": "ThrowStatement", + "autofixable": true, + "autofix": [ + { + "start": 1144, + "end": 1145, + "replacementText": "new Error('', 1)" + } + ] + }, + { + "line": 41, + "column": 21, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 41, + "column": 31, + "problem": "ObjectTypeLiteral", + "autofixable": false + }, + { + "line": 41, + "column": 42, + "problem": "UnknownType", + "autofixable": false + }, + { + "line": 41, + "column": 54, + "problem": "IndexedAccessType", + "autofixable": false + }, + { + "line": 45, + "column": 1, + "problem": "InterfaceOrEnumMerging", + "autofixable": false + }, + { + "line": 51, + "column": 19, + "problem": "ConditionalType", + "autofixable": false + }, + { + "line": 51, + "column": 29, + "problem": "AnyType", + "autofixable": false + }, + { + "line": 51, + "column": 37, + "problem": "IndexedAccessType", + "autofixable": false + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/delete_operator.ts.relax.json b/linter-4.2/test/delete_operator.ts.relax.json index 1af2b8cadafc701c48d76ec63b28e362757fb9f1..2addafd48ca717f4504bee9d7708f24964da3a1e 100644 --- a/linter-4.2/test/delete_operator.ts.relax.json +++ b/linter-4.2/test/delete_operator.ts.relax.json @@ -73,6 +73,11 @@ "line": 44, "column": 23, "problem": "DeleteOperator" + }, + { + "line": 44, + "column": 30, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/delete_operator.ts.strict.json b/linter-4.2/test/delete_operator.ts.strict.json index 1af2b8cadafc701c48d76ec63b28e362757fb9f1..2addafd48ca717f4504bee9d7708f24964da3a1e 100644 --- a/linter-4.2/test/delete_operator.ts.strict.json +++ b/linter-4.2/test/delete_operator.ts.strict.json @@ -73,6 +73,11 @@ "line": 44, "column": 23, "problem": "DeleteOperator" + }, + { + "line": 44, + "column": 30, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/func_def_params.ts.relax.json b/linter-4.2/test/func_def_params.ts.relax.json deleted file mode 100644 index b06227021f092f20110cdafb2a6d660605c2ff96..0000000000000000000000000000000000000000 --- a/linter-4.2/test/func_def_params.ts.relax.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "copyright": [ - "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", - "Licensed under the Apache License, Version 2.0 (the 'License');", - "you may not use this file except in compliance with the License.", - "You may obtain a copy of the License at", - "", - "http://www.apache.org/licenses/LICENSE-2.0", - "", - "Unless required by applicable law or agreed to in writing, software", - "distributed under the License is distributed on an 'AS IS' BASIS,", - "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", - "See the License for the specific language governing permissions and", - "limitations under the License." - ], - "nodes": [] -} \ No newline at end of file diff --git a/linter-4.2/test/func_inferred_type_args.ts.relax.json b/linter-4.2/test/func_inferred_type_args.ts.relax.json index 6138cc504f943977055f24d208a0a07a8672a255..dcc02fc62df7e0b8acc65c242ac3e48d467c82c9 100644 --- a/linter-4.2/test/func_inferred_type_args.ts.relax.json +++ b/linter-4.2/test/func_inferred_type_args.ts.relax.json @@ -123,6 +123,11 @@ "line": 60, "column": 18, "problem": "TupleType" + }, + { + "line": 61, + "column": 1, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/func_inferred_type_args.ts.strict.json b/linter-4.2/test/func_inferred_type_args.ts.strict.json index 6138cc504f943977055f24d208a0a07a8672a255..dcc02fc62df7e0b8acc65c242ac3e48d467c82c9 100644 --- a/linter-4.2/test/func_inferred_type_args.ts.strict.json +++ b/linter-4.2/test/func_inferred_type_args.ts.strict.json @@ -123,6 +123,11 @@ "line": 60, "column": 18, "problem": "TupleType" + }, + { + "line": 61, + "column": 1, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/function_overload.ts.strict.json b/linter-4.2/test/function_overload.ts.strict.json index 6753bf2e36442679b255ef4642bbe403ddf7a29d..13aacbf8bddbb173935d625cc6d39095c506a328 100644 --- a/linter-4.2/test/function_overload.ts.strict.json +++ b/linter-4.2/test/function_overload.ts.strict.json @@ -14,16 +14,6 @@ "limitations under the License." ], "nodes": [ - { - "line": 18, - "column": 41, - "problem": "FuncOptionalParams" - }, - { - "line": 18, - "column": 53, - "problem": "FuncOptionalParams" - }, { "line": 37, "column": 19, diff --git a/linter-4.2/test/functions.ts.autofix.json b/linter-4.2/test/functions.ts.autofix.json index 1494a96b12782b37e06e9f07b66b79f5d1acd8f3..9e96283aa935b70c7c6a9ac6b5dc03dbf8fec795 100644 --- a/linter-4.2/test/functions.ts.autofix.json +++ b/linter-4.2/test/functions.ts.autofix.json @@ -208,6 +208,12 @@ "column": 1, "problem": "FunctionContainsThis", "autofixable": false + }, + { + "line": 107, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false } ] } \ No newline at end of file diff --git a/linter-4.2/test/functions.ts.relax.json b/linter-4.2/test/functions.ts.relax.json index bd460a79893a2a7bdc1d8ca738c6316f122c2497..6e7f7d3992d8cc25679adaa76e811260b51342f6 100644 --- a/linter-4.2/test/functions.ts.relax.json +++ b/linter-4.2/test/functions.ts.relax.json @@ -28,6 +28,11 @@ "line": 103, "column": 1, "problem": "FunctionContainsThis" + }, + { + "line": 107, + "column": 3, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/functions.ts.strict.json b/linter-4.2/test/functions.ts.strict.json index 66f8cf949ecb65b91998e6c0a2fc18b4da229a4d..726203465d60fd317a31734afd93b83c23acb338 100644 --- a/linter-4.2/test/functions.ts.strict.json +++ b/linter-4.2/test/functions.ts.strict.json @@ -123,6 +123,11 @@ "line": 103, "column": 1, "problem": "FunctionContainsThis" + }, + { + "line": 107, + "column": 3, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/instanceof.ts b/linter-4.2/test/instanceof.ts index 7f04b131b0decc7c28b286b74fdafa53ddd97a01..2289a2292c1ff992a31e34c8bc412a68d6d45b19 100644 --- a/linter-4.2/test/instanceof.ts +++ b/linter-4.2/test/instanceof.ts @@ -28,3 +28,13 @@ const e = (5.0 as number) instanceof Number; // false const f = (X | String) instanceof Object; const g = 3 instanceof Number; + + +let bad2Number = 5.0 as Number; +let pi = 3.14; +bad2Number = pi as Number; + +let bad2Boolean = true as Boolean; +let bad2Boolean = a as Boolean; + + diff --git a/linter-4.2/test/instanceof.ts.relax.json b/linter-4.2/test/instanceof.ts.relax.json index f9d548166e325ea9f11bd1b7a346c6e59aa69005..cec2272db2f3556779065fb98f18b9e14476e882 100644 --- a/linter-4.2/test/instanceof.ts.relax.json +++ b/linter-4.2/test/instanceof.ts.relax.json @@ -24,11 +24,6 @@ "column": 11, "problem": "InstanceofUnsupported" }, - { - "line": 28, - "column": 12, - "problem": "BitOpWithWrongType" - }, { "line": 30, "column": 11, diff --git a/linter-4.2/test/instanceof.ts.strict.json b/linter-4.2/test/instanceof.ts.strict.json index f9d548166e325ea9f11bd1b7a346c6e59aa69005..e3679adf582df4f706be78a9cc70421a3cb7d0ad 100644 --- a/linter-4.2/test/instanceof.ts.strict.json +++ b/linter-4.2/test/instanceof.ts.strict.json @@ -24,15 +24,30 @@ "column": 11, "problem": "InstanceofUnsupported" }, - { - "line": 28, - "column": 12, - "problem": "BitOpWithWrongType" - }, { "line": 30, "column": 11, "problem": "InstanceofUnsupported" + }, + { + "line": 33, + "column": 18, + "problem": "TypeAssertion" + }, + { + "line": 35, + "column": 14, + "problem": "TypeAssertion" + }, + { + "line": 37, + "column": 19, + "problem": "TypeAssertion" + }, + { + "line": 38, + "column": 19, + "problem": "TypeAssertion" } ] } \ No newline at end of file diff --git a/linter-4.2/test/literals_as_prop_names.ts.autofix.json b/linter-4.2/test/literals_as_prop_names.ts.autofix.json index 3527aaf7465b527a65512037333cfc404e713afa..6fa935a2208899e916a699c90cf1eef681d0a801 100644 --- a/linter-4.2/test/literals_as_prop_names.ts.autofix.json +++ b/linter-4.2/test/literals_as_prop_names.ts.autofix.json @@ -168,6 +168,30 @@ "column": 13, "problem": "ObjectLiteralNoContextType", "autofixable": false + }, + { + "line": 34, + "column": 11, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 35, + "column": 3, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 55, + "column": 12, + "problem": "StrictDiagnostic", + "autofixable": false + }, + { + "line": 56, + "column": 12, + "problem": "StrictDiagnostic", + "autofixable": false } ] } \ No newline at end of file diff --git a/linter-4.2/test/literals_as_prop_names.ts.relax.json b/linter-4.2/test/literals_as_prop_names.ts.relax.json index 212bf339f67d7baf457ee2a3ad9c9d9ca64ad794..4093c99242f47749d47f36f5d59a29c50bafe406 100644 --- a/linter-4.2/test/literals_as_prop_names.ts.relax.json +++ b/linter-4.2/test/literals_as_prop_names.ts.relax.json @@ -23,6 +23,26 @@ "line": 59, "column": 13, "problem": "ObjectLiteralNoContextType" + }, + { + "line": 34, + "column": 11, + "problem": "StrictDiagnostic" + }, + { + "line": 35, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 55, + "column": 12, + "problem": "StrictDiagnostic" + }, + { + "line": 56, + "column": 12, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/literals_as_prop_names.ts.strict.json b/linter-4.2/test/literals_as_prop_names.ts.strict.json index f4cd055c3c5de6ee9d1f7d6befa4712990fcd202..da9706e0fd41657f3f5a78aa0febee3fc010174f 100644 --- a/linter-4.2/test/literals_as_prop_names.ts.strict.json +++ b/linter-4.2/test/literals_as_prop_names.ts.strict.json @@ -78,6 +78,26 @@ "line": 59, "column": 13, "problem": "ObjectLiteralNoContextType" + }, + { + "line": 34, + "column": 11, + "problem": "StrictDiagnostic" + }, + { + "line": 35, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 55, + "column": 12, + "problem": "StrictDiagnostic" + }, + { + "line": 56, + "column": 12, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/modules.ts b/linter-4.2/test/modules.ts index 925584a775b9fa35d502f26764fddddaf266ea3d..e183bc2a35a5360eef4545dd4446526404ce8c9b 100644 --- a/linter-4.2/test/modules.ts +++ b/linter-4.2/test/modules.ts @@ -91,16 +91,16 @@ export default class MyClass {} // default class export // type-only import import type { APIResponseType } from './api'; import type * as P from 'foo'; +import { type T1, type T2 as T3 } from 'foobar'; // type-only export export type { TypeA as TypeB }; +export { type TypeFoo as TypeBar }; -class SomeClass {} -export { SomeClass as AnotherClass }; // Export renaming - -export { Foo, Bar as Baz, Goo as Zar }; // Export list declaration - -export { SomeFunction } from 'module'; // Re-exporting +// Re-exporting +export * from 'module1'; // Not supported +export * as Utilities from 'module2'; // Ok +export { SomeFunction, SomeType as OtherType } from 'module3'; // Ok class Point {} export = Point; diff --git a/linter-4.2/test/modules.ts.autofix.json b/linter-4.2/test/modules.ts.autofix.json new file mode 100644 index 0000000000000000000000000000000000000000..a036251cd9c7a837c948cc111cfb38cde46c48bc --- /dev/null +++ b/linter-4.2/test/modules.ts.autofix.json @@ -0,0 +1,184 @@ +{ + "copyright": [ + "Copyright (c) 2022-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 16, + "column": 1, + "problem": "ImportFromPath", + "autofixable": false + }, + { + "line": 22, + "column": 3, + "problem": "WithStatement", + "autofixable": false + }, + { + "line": 43, + "column": 3, + "problem": "NonDeclarationInNamespace", + "autofixable": false + }, + { + "line": 44, + "column": 3, + "problem": "NonDeclarationInNamespace", + "autofixable": false + }, + { + "line": 64, + "column": 11, + "problem": "NamespaceAsObject", + "autofixable": false + }, + { + "line": 67, + "column": 15, + "problem": "NamespaceAsObject", + "autofixable": false + }, + { + "line": 70, + "column": 18, + "problem": "TypeQuery", + "autofixable": false + }, + { + "line": 70, + "column": 25, + "problem": "NamespaceAsObject", + "autofixable": false + }, + { + "line": 73, + "column": 6, + "problem": "NamespaceAsObject", + "autofixable": false + }, + { + "line": 75, + "column": 20, + "problem": "TypeQuery", + "autofixable": false + }, + { + "line": 75, + "column": 29, + "problem": "NamespaceAsObject", + "autofixable": false + }, + { + "line": 78, + "column": 9, + "problem": "NamespaceAsObject", + "autofixable": false + }, + { + "line": 80, + "column": 10, + "problem": "DefaultImport", + "autofixable": true, + "autofix": [ + { + "start": 1637, + "end": 1655, + "replacementText": "def" + } + ] + }, + { + "line": 89, + "column": 1, + "problem": "DeclWithDuplicateName", + "autofixable": false + }, + { + "line": 92, + "column": 8, + "problem": "TypeOnlyImport", + "autofixable": true, + "autofix": [ + { + "start": 1923, + "end": 1947, + "replacementText": "{ APIResponseType }" + } + ] + }, + { + "line": 93, + "column": 8, + "problem": "TypeOnlyImport", + "autofixable": true, + "autofix": [ + { + "start": 1969, + "end": 1980, + "replacementText": "* as P" + } + ] + }, + { + "line": 97, + "column": 1, + "problem": "TypeOnlyExport", + "autofixable": true, + "autofix": [ + { + "start": 2063, + "end": 2094, + "replacementText": "export { TypeA as TypeB };" + } + ] + }, + { + "line": 101, + "column": 1, + "problem": "LimitedReExporting", + "autofixable": false + }, + { + "line": 106, + "column": 1, + "problem": "ExportAssignment", + "autofixable": false + }, + { + "line": 108, + "column": 1, + "problem": "ImportAssignment", + "autofixable": false + }, + { + "line": 109, + "column": 1, + "problem": "ImportAssignment", + "autofixable": false + }, + { + "line": 111, + "column": 5, + "problem": "ImportAssignment", + "autofixable": false + }, + { + "line": 112, + "column": 7, + "problem": "ImportAssignment", + "autofixable": false + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/modules.ts.autofix.skip b/linter-4.2/test/modules.ts.autofix.skip deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/linter-4.2/test/modules.ts.relax.json b/linter-4.2/test/modules.ts.relax.json index 063a63432fd8d4289c71aab7f6571197351052b6..d9b40d81005df642df972f6d81189c3229dc1c7a 100644 --- a/linter-4.2/test/modules.ts.relax.json +++ b/linter-4.2/test/modules.ts.relax.json @@ -75,9 +75,9 @@ "problem": "NamespaceAsObject" }, { - "line": 103, + "line": 101, "column": 1, - "problem": "ReExporting" + "problem": "LimitedReExporting" }, { "line": 106, diff --git a/linter-4.2/test/modules.ts.strict.json b/linter-4.2/test/modules.ts.strict.json index fb3ddc25f6cc5fd49b13ef7037516f347164bc2a..a3207e3871d51af33d18c4c887af85aebcb70c52 100644 --- a/linter-4.2/test/modules.ts.strict.json +++ b/linter-4.2/test/modules.ts.strict.json @@ -95,54 +95,14 @@ "problem": "TypeOnlyImport" }, { - "line": 96, + "line": 97, "column": 1, "problem": "TypeOnlyExport" }, - { - "line": 96, - "column": 13, - "problem": "ExportListDeclaration" - }, - { - "line": 96, - "column": 15, - "problem": "ExportRenaming" - }, - { - "line": 99, - "column": 8, - "problem": "ExportListDeclaration" - }, - { - "line": 99, - "column": 10, - "problem": "ExportRenaming" - }, - { - "line": 101, - "column": 8, - "problem": "ExportListDeclaration" - }, - { - "line": 101, - "column": 15, - "problem": "ExportRenaming" - }, { "line": 101, - "column": 27, - "problem": "ExportRenaming" - }, - { - "line": 103, "column": 1, - "problem": "ReExporting" - }, - { - "line": 103, - "column": 8, - "problem": "ExportListDeclaration" + "problem": "LimitedReExporting" }, { "line": 106, diff --git a/linter-4.2/test/non_initializable_prop_decorators.ts b/linter-4.2/test/non_initializable_prop_decorators.ts new file mode 100644 index 0000000000000000000000000000000000000000..93fb59f22ddca0495e0596bfadf778149c4b45bf --- /dev/null +++ b/linter-4.2/test/non_initializable_prop_decorators.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +type PropDecorator = (target: any, propertyKey: any) => void; +type PropDecoratorWithCall = PropDecorator & ((val: string) => PropDecorator); + +let Prop: PropDecorator; +let Link: PropDecorator; +let ObjectLink: PropDecorator; +let Consume: PropDecoratorWithCall; +let Provide: PropDecoratorWithCall; + +class Decorators { + @Prop + p: number; // Not ok + + @Link + l: number; + + @ObjectLink + public ol: number; + + @Consume + private c: number; + + @Consume('foo') + c2: number; + + @Provide + pr: number // Not ok + + @Provide('bar') + pr2: number; // Not ok + + @Prop + @Provide + @Link + f: number; +} \ No newline at end of file diff --git a/linter-4.2/test/catch_clause.ts.autofix.skip b/linter-4.2/test/non_initializable_prop_decorators.ts.autofix.skip similarity index 100% rename from linter-4.2/test/catch_clause.ts.autofix.skip rename to linter-4.2/test/non_initializable_prop_decorators.ts.autofix.skip diff --git a/linter-4.2/test/non_initializable_prop_decorators.ts.relax.json b/linter-4.2/test/non_initializable_prop_decorators.ts.relax.json new file mode 100644 index 0000000000000000000000000000000000000000..763779041f2f7df9466d5b35c61d25c2c3b4a975 --- /dev/null +++ b/linter-4.2/test/non_initializable_prop_decorators.ts.relax.json @@ -0,0 +1,48 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 16, + "column": 31, + "problem": "AnyType" + }, + { + "line": 16, + "column": 49, + "problem": "AnyType" + }, + { + "line": 17, + "column": 30, + "problem": "IntersectionType" + }, + { + "line": 27, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 42, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 45, + "column": 3, + "problem": "StrictDiagnostic" + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/non_initializable_prop_decorators.ts.strict.json b/linter-4.2/test/non_initializable_prop_decorators.ts.strict.json new file mode 100644 index 0000000000000000000000000000000000000000..763779041f2f7df9466d5b35c61d25c2c3b4a975 --- /dev/null +++ b/linter-4.2/test/non_initializable_prop_decorators.ts.strict.json @@ -0,0 +1,48 @@ +{ + "copyright": [ + "Copyright (c) 2023-2023 Huawei Device Co., Ltd.", + "Licensed under the Apache License, Version 2.0 (the 'License');", + "you may not use this file except in compliance with the License.", + "You may obtain a copy of the License at", + "", + "http://www.apache.org/licenses/LICENSE-2.0", + "", + "Unless required by applicable law or agreed to in writing, software", + "distributed under the License is distributed on an 'AS IS' BASIS,", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", + "See the License for the specific language governing permissions and", + "limitations under the License." + ], + "nodes": [ + { + "line": 16, + "column": 31, + "problem": "AnyType" + }, + { + "line": 16, + "column": 49, + "problem": "AnyType" + }, + { + "line": 17, + "column": 30, + "problem": "IntersectionType" + }, + { + "line": 27, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 42, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 45, + "column": 3, + "problem": "StrictDiagnostic" + } + ] +} \ No newline at end of file diff --git a/linter-4.2/test/object_literals.ts.relax.json b/linter-4.2/test/object_literals.ts.relax.json index 0ac869033594242bb3b3bedb2a457ff836f33a1b..fc1fdd35c4ab152a289acbfaf0c19567503ddaec 100644 --- a/linter-4.2/test/object_literals.ts.relax.json +++ b/linter-4.2/test/object_literals.ts.relax.json @@ -253,6 +253,31 @@ "line": 129, "column": 5, "problem": "ObjectLiteralNoContextType" + }, + { + "line": 22, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 23, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 27, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 29, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 30, + "column": 3, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/object_literals.ts.strict.json b/linter-4.2/test/object_literals.ts.strict.json index c4c6f0b2ffe1dd2aad40e5bd7a44daad5a69823a..d301f909035f0f13d2cd84e01bfe378b3f716879 100644 --- a/linter-4.2/test/object_literals.ts.strict.json +++ b/linter-4.2/test/object_literals.ts.strict.json @@ -303,6 +303,31 @@ "line": 129, "column": 5, "problem": "ObjectLiteralNoContextType" + }, + { + "line": 22, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 23, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 27, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 29, + "column": 3, + "problem": "StrictDiagnostic" + }, + { + "line": 30, + "column": 3, + "problem": "StrictDiagnostic" } ] } \ No newline at end of file diff --git a/linter-4.2/test/object_literals_2.ts b/linter-4.2/test/object_literals_2.ts index ebd3cf272c657fa1d4c6439865adba43c28fd3d9..7960117c0925db82928f62040cb0fbb6d0601fc2 100644 --- a/linter-4.2/test/object_literals_2.ts +++ b/linter-4.2/test/object_literals_2.ts @@ -53,14 +53,8 @@ let d: D = { } } -// Special case: initializing of records: -let m1: Record = { - 'a': 33, - 'b': 44 -} - // Restrictions of classes that can be initialized with literal -// Default initalizable class. +// Default initializable class. class C1 { n: number = 0; s?: string; @@ -77,7 +71,7 @@ class C2 { let c5: C2 = {s: 'foo'} // OK in TS, CTE in ArkTS -// All class fields are accessibel at the point of initialization. +// All class fields are accessible at the point of initialization. class C3 { private n: number = 0; public s: string = ''; @@ -96,7 +90,7 @@ let c7: C4 = {n: 42, s: 'foo'}; // OK in TS, CTE in ArkTS abstract class A {} let a: A = {}; // OK in TS, CTE in ArkTS -// Class declares no methods, apart from optionally declaraed constructors and setters. +// Class declares no methods, apart from optionally declared constructors and setters. class C5 { n: number = 0; s: string = ''; @@ -128,7 +122,7 @@ class C7 { let o1: Object = {s: '', n: 42} // OK in TS, CTE in ArkTS: no fields 'n' and 's' in Object let o2: object = {n: 42, s: ''} // OK in TS, CTE in ArkTS: no fields 'n' and 's' in object -// If initialzed class is inherited from another class, the base class must also be literal-initialzable, +// If initialized class is inherited from another class, the base class must also be literal-initializable, // and initialization should happen from the 'glattened' literal: class Base { n: number = 0; @@ -140,7 +134,7 @@ class Derived extends Base { let d2: Derived = {n: 42, s: ''}; -// Intrface should not declare methods, only properties are allowed. +// Interface should not declare methods, only properties are allowed. interface I { n: number; s: string; @@ -149,10 +143,52 @@ interface I { let i: I = {n: 42, s: '', f: () => {console.log('I.f is called')}} // OK in TypeScript, CTE in ArkTS -// Interface properties of reference types must be default-initlizable: +// Interface properties of reference types must be default-initializable: interface I2 { n: number; s: string; // Assuming that 'string' is an alias for 'String', and there is String() constructor (what is true). } -let i2: I2 = {n: 42, s: ''}; \ No newline at end of file +let i2: I2 = {n: 42, s: ''}; + +interface CompilerOptions { + strict?: boolean; + sourcePath?: string; + targetPath?: string; +} + +const options: CompilerOptions = { // OK, as 'targetPath' field is optional + strict: true, + sourcePath: './src', +}; + +// Special case: initializing Record +let m1: Record = { + 'a': 33, + 'b': 44 +}; + +class Person { + name: string = "" + age: number = 0 + location: string = "" +} + +type OptionalPerson = Person | undefined; +let persons : Record = { + "Alice": { name: "Alice", age: 32, location: "Shanghai" }, + "Bob": { name: "Bob", age: 48, location: "New York" } +}; +let persons2 : Record = { + "Alice": { name: "Alice", age: 32, location: "Shanghai" }, + "Bob": { name: "Bob", age: 48, location: "New York" } +}; + +// Special case: initializing Partial +interface Style { + color: number; +} + +function applyStyle(arg: Partial