diff --git a/.gitignore b/.gitignore
index 2a23da7872b255928df7d4d3d547cc6a41e8326f..c8ed6fcdfeec7e6cba537e35c399575a5a839322 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ ets2panda/linter*/**/.vscode
ets2panda/linter*/**/.cache
ets2panda/linter*/test/**/results
ets2panda/linter*/test_rules/**/results
+ets2panda/linter*/test_regression/**/results
ets2panda/linter*/**/package-lock.json
**/compile_commands.json
.cache
diff --git a/ets2panda/linter-4.2/README.md b/ets2panda/linter-4.2/README.md
index 3983b1f55123e84c069cdba99b25d334aaae64ed..9c637a534ebb9f839d09fc10853aaf72337a5fbe 100644
--- a/ets2panda/linter-4.2/README.md
+++ b/ets2panda/linter-4.2/README.md
@@ -1,14 +1,17 @@
# TypeScript linter
-Typescript linter ( further mentioned as 'linter' ) is a tool to check typescript sources and find language elements
-and constructions which are deprecated to use in a purpose to migrate sources to STS.
+
+Typescript linter ( further mentioned as 'linter' ) is a tool to check typescript sources and find language elements
+and constructions which are deprecated to use in a purpose to migrate sources to STS.
The linter is currently under development.
## Prerequisits
### Visual Studio Code
+
For development, it's recommended to use `VS Code`, as it has a full built-in support for TypeScript language.
### NodeJS and NPM
+
Install the latest stable version of `NodeJS` and `NPM`. It is recommended using a `Node version manager` to install Node and NPM ([nvm](https://github.com/nvm-sh/nvm) for Linux; [nvm-windows](https://github.com/coreybutler/nvm-windows) for windows - v1.1.9 is the most stable). You can also follow the [official guide](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
## Building
@@ -26,7 +29,9 @@ npm run build
```
## Running
+
Run the following command from the same directory:
+
```bash
node dist/tslinter.js [options] [input files]
```
@@ -56,6 +61,7 @@ To prevent command line buffer overflow, response file may be used. It is specif
## Running tests
Run the following command:
+
```bash
npm test
```
diff --git a/ets2panda/linter-4.2/cookbook_convertor/README.md b/ets2panda/linter-4.2/cookbook_convertor/README.md
index d54c35d4a7d0515d8a2809dcfd0daee54d366b31..3a63ff481687d542e91f5f944037a3af1e909b64 100644
--- a/ets2panda/linter-4.2/cookbook_convertor/README.md
+++ b/ets2panda/linter-4.2/cookbook_convertor/README.md
@@ -1,4 +1,5 @@
# Cookbook Convertor
+
Cookbook convertor is a tool that generates rule descriptions for TS Linter from Cookbook receipts. Convertor accepts RST file that contains the sources for Cookbook receipts.
## Prerequisits
@@ -20,7 +21,9 @@ npm run build
```
## Running
+
Run the following command from the same directory:
+
```bash
node build/cookbook_convertor.js [rst_file]
```
diff --git a/ets2panda/linter-4.2/cookbook_convertor/res/recipes.rst b/ets2panda/linter-4.2/cookbook_convertor/res/recipes.rst
index 3c1856c0cc472cdf0d01d8a21a758482c206fb85..9f4bbd13d580b1017a73fff43c8e32cb4fea2e19 100644
--- a/ets2panda/linter-4.2/cookbook_convertor/res/recipes.rst
+++ b/ets2panda/linter-4.2/cookbook_convertor/res/recipes.rst
@@ -685,49 +685,6 @@ All types must be specified explicitly.
let a: A
let b: A
-.. _R024:
-
-|CB_R| #24: Optional arguments are not supported
-------------------------------------------------
-
-|CB_RULE|
-~~~~~~~~~
-
-Currently, |LANG| does not support optional parameters. Specify an
-optional parameter as a parameter of a nullable type with the
-default value ``null``. Default parameter values are supported for all types.
-
-|CB_BAD|
-~~~~~~~~
-
-.. code-block:: 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)
- }
-
-|CB_OK|
-~~~~~~~
-
-.. code-block:: typescript
-
- // Optional parameters are not supported,
- // but you can assign a default value ``null`` for the parameter:
- function f(x: number | null = null) {
- console.log(x); // log null or number
- }
-
- // x is a required argument with the default value:
- function g(x: number = 1) {
- console.log(x);
- }
-
.. _R025:
|CB_R| #25: Declaring fields in ``constructor`` is not supported
@@ -1095,56 +1052,6 @@ public APIs and decide whether such types are identical. Use other mechanisms
* :ref:`R031`
* :ref:`R035`
-.. _R033:
-
-|CB_R| #33: Optional properties are not supported
--------------------------------------------------
-
-|CB_RULE|
-~~~~~~~~~
-
-|LANG| does not support optional properties. Use properties with default values.
-Use properties of nullable types and the default ``null`` value to distinguish
-whether a value is set or not.
-
-|CB_BAD|
-~~~~~~~~
-
-.. code-block:: typescript
-
- interface CompilerOptions {
- strict?: boolean
- sourcePath?: string
- targetPath?: string
- }
-
- var options: CompilerOptions = {
- strict: true,
- sourcepath: "./src",
- }
- if option.targetPath == undefined {
- // set default
- }
-
-|CB_OK|
-~~~~~~~
-
-.. code:: typescript
-
- interface CompilerOptions {
- strict: boolean = false
- sourcePath: string = ""
- targetPath: string | null = null
- }
-
- let options: CompilerOptions = {
- strict: true,
- sourcepath: "./src",
- }
- if option.targetPath == null {
- // set default
- }
-
.. _R034:
|CB_R| #34: Generic functions must be called with explicit type specialization
@@ -2823,7 +2730,6 @@ library (for example, ``Int32Array``) are also supported.
for (let n of a) {
console.log(n)
}
-
|CB_SEE|
~~~~~~~~
@@ -4180,7 +4086,6 @@ Differences are described in separate recipes.
* :ref:`R120`
* :ref:`R121`
* :ref:`R122`
-* :ref:`R123`
* :ref:`R124`
* :ref:`R125`
* :ref:`R126`
@@ -4433,64 +4338,6 @@ Use explicit ``import ... from ...`` instead.
* :ref:`R120`
-.. _R123:
-
-|CB_R| #123: Renaming in export declarations is not supported
--------------------------------------------------------------
-
-|CB_RULE|
-~~~~~~~~~
-
-|LANG| does not support renaming in export declarations. Similar effect
-can be achieved through setting an alias for the exported entity.
-
-|CB_BAD|
-~~~~~~~~
-
-.. code-block:: typescript
-
- // file1.ts
- class MyClass {
- // ...
- }
-
- export { MyClass as RenamedClass }
-
- // file2.ts
- import { RenamedClass } from "./file1"
-
- function main(): void {
- const myObject = new RenamedClass()
- // ...
- }
-
-|CB_OK|
-~~~~~~~
-
-.. code-block:: typescript
-
- // module1
- class MyClass {
- // ...
- }
-
- export RenamedClass = MyClass
-
- // module2
- import RenamedClass from "./module1"
-
- function main(): void {
- const myObject = new RenamedClass()
- // ...
- }
-
-|CB_SEE|
-~~~~~~~~
-
-* :ref:`R124`
-* :ref:`R125`
-* :ref:`R126`
-
.. _R124:
|CB_R| #124: Export list declaration is not supported
@@ -4524,7 +4371,6 @@ entities must be explicitly annotated with the ``export`` keyword.
|CB_SEE|
~~~~~~~~
-* :ref:`R123`
* :ref:`R125`
* :ref:`R126`
@@ -4579,7 +4425,6 @@ imported explicitly from the modules that export them.
|CB_SEE|
~~~~~~~~
-* :ref:`R123`
* :ref:`R124`
* :ref:`R126`
@@ -4631,7 +4476,6 @@ Use regular ``export`` / ``import`` instead.
|CB_SEE|
~~~~~~~~
-* :ref:`R123`
* :ref:`R124`
* :ref:`R125`
diff --git a/ets2panda/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts b/ets2panda/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts
index d6e672ebff585f03aa6bcaf4354dae59e10eaf46..58932f507750bde7b477bf56ac0aad2ff76a4306 100644
--- a/ets2panda/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts
+++ b/ets2panda/linter-4.2/cookbook_convertor/src/cookbook_convertor.ts
@@ -16,448 +16,473 @@
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
-
-const COPYRIGHT_HEADER = "/* \n\
+const COPYRIGHT_HEADER =
+ '/* \n\
* Copyright (c) 2022-2023 Huawei Device Co., Ltd. \n\
- * Licensed under the Apache License, Version 2.0 (the \"License\"); \n\
+ * Licensed under the Apache License, Version 2.0 (the "License"); \n\
* you may not use this file except in compliance with the License. \n\
* You may obtain a copy of the License at \n\
* \n\
* http://www.apache.org/licenses/LICENSE-2.0 \n\
* \n\
* Unless required by applicable law or agreed to in writing, software \n\
- * distributed under the License is distributed on an \"AS IS\" BASIS, \n\
+ * distributed under the License is distributed on an "AS IS" BASIS, \n\
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n\
* See the License for the specific language governing permissions and \n\
* limitations under the License. \n\
*/ \n\
-";
+';
-const CODE_PROLOGUE = "export const cookBookMsg: string[] = [];\n\
+const CODE_PROLOGUE =
+ 'export const cookBookMsg: string[] = [];\n\
export const cookBookTag: string[] = [];\n\
\n\
-for( let i = 0; i < 148; i++) {\n\
- cookBookMsg[ i ] = '';\n\
+for( let i = 0; i <= 150; i++) {\n\
+ cookBookMsg[ i ] = \'\';\n\
}\n\
-";
-
-// HTML tegs
-const T_BR = " ";
-const T_UNDERLINE = "";
-const T_END_UNDERLINE = "";
-const T_BOLD = "";
-const T_END_BOLD = "";
-const T_ITALIC = "";
-const T_END_ITALIC = "";
-const T_CODE = "";
-const T_END_CODE = "";
-const T_NBSP = " ";
+';
+
+// HTML tags
+const T_BR = ' ';
+const T_BOLD = '';
+const T_END_BOLD = '';
+const T_CODE = '';
+const T_END_CODE = '';
+const T_NBSP = ' ';
const T_HR = '
';
// RST substititions
-const CB_ = "|CB_";
-const CB_R = "|CB_R|";
-const CB_RULE = "|CB_RULE|";
-const CB_BAD = "|CB_BAD|";
-const CB_OK = "|CB_OK|";
-const CB_ERROR = "|CB_ERROR|"; //replace:: **Severity: error**
-const CB_WARNING = "|CB_WARNING|"; //replace:: **Severity: warning**
-const CB_SEE = "|CB_SEE|";
-const CB_REF = ":ref:";
-const CB_META = ".. meta";
-
-const NEW_REC_HEADER = ".. _R";
-const CODE_BLOCK = ".. code"; // should be ".. code-block" but in some places there is error in doc file
-
-let MAKE_MD = false; // flag to generate .md files
-
-let doc_lines: string[];
-let _line:number
+const CB_PREFIX = '|CB_PREFIX';
+const CB_R = '|CB_R|';
+const CB_RULE = '|CB_RULE|';
+const CB_BAD = '|CB_BAD|';
+const CB_OK = '|CB_OK|';
+// replace:: **Severity: error**
+const CB_ERROR = '|CB_ERROR|';
+// replace:: **Severity: warning**
+const CB_WARNING = '|CB_WARNING|';
+const CB_SEE = '|CB_SEE|';
+const CB_REF = ':ref:';
+const CB_META = '.. meta';
+const CB_FIX = ':fix:';
+
+const NEW_REC_HEADER = /.. _R\d\d\d:/;
+// should be ".. code-block" but in some places there is error in doc file
+const CODE_BLOCK = '.. code';
+
+let docLines: string[];
+let curLine: number;
let recNum: number;
-let tegs: string[] = [];
-let ruleNames: string[] = [];
-let cooks: string[] = [];
+const tegs: string[] = [];
+const ruleNames: string[] = [];
let mdText: string[] = [];
+const fixTitles: Map = new Map();
+// continue line
+const CL = ' \\';
+const STR_DLMTR = '\'';
-
-const CL = " \\"; // continue line
-const STR_DLMTR = "\'";
-
-
-function syncReadFile(filename: string) {
+function syncReadFile(filename: string): string[] {
const contents = readFileSync(filename, 'utf-8');
- doc_lines = contents.split(/\r?\n/);
+ docLines = contents.split(/\r?\n/);
-// make table of rule names
- _line = 0;
+ // make table of rule names
+ curLine = 0;
let ruleNum = -1;
- while( _line < doc_lines.length ) {
- if(doc_lines[ _line ].startsWith( NEW_REC_HEADER ) ) {
-console.log(">>>>>>> START RULE " + doc_lines[ _line ].split( NEW_REC_HEADER )[1])
- ruleNum = Number((doc_lines[ _line ].split( NEW_REC_HEADER )[1]).split(":")[0]);
-console.log(" NUMBER: " + ruleNum)
- }
- if( doc_lines[ _line ].startsWith( CB_R ) ) {
- let line = doc_lines[ _line ].split( CB_R )[1];
- ruleNames[ ruleNum ] = line; //line.split(':')[1];
- _line++;
+ while (curLine < docLines.length) {
+ const line = docLines[curLine];
+ if (NEW_REC_HEADER.test(line)) {
+ ruleNum = Number(line.replace(/\D/g, ''));
+ console.log('>>>>>>> START RULE ' + ruleNum + ':');
+ console.log(' NUMBER: ' + ruleNum);
+ }
+ if (docLines[curLine].startsWith(CB_R)) {
+ let line = docLines[curLine].split(CB_R)[1];
+ ruleNames[ruleNum] = line;
+ curLine++;
needHeader();
- if( doc_lines[ _line ].startsWith( CB_RULE ) ) {
- line = doc_lines[ _line ].trim().replace( CB_RULE, "").trim();
- ruleNames[ ruleNum ] = ruleNames[ ruleNum ] + " (" + line + ")";
+ if (docLines[curLine].startsWith(CB_RULE)) {
+ line = docLines[curLine].trim().replace(CB_RULE, '').
+ trim();
+ ruleNames[ruleNum] = ruleNames[ruleNum] + ' (' + line + ')';
}
}
- _line ++;
+ curLine++;
}
// scan text
- _line = 0;
- while( _line < doc_lines.length ) {
- skipEmptyLines();
- if( doc_lines[_line].startsWith(NEW_REC_HEADER)) {
- makeRecept();
- }
- else
- _line++;
+ curLine = 0;
+ while (curLine < docLines.length) {
+ skipEmptyLines();
+ const line = docLines[curLine];
+ if (NEW_REC_HEADER.test(line)) {
+ makeRecipe();
+ } else {
+ curLine++;
+ }
}
- return doc_lines;
+ return docLines;
}
+/*
+ *
+ * utility functions
+ *
+ */
-//
-// utility functions
-//
-
-function replaceAll( s: string, from: string, to: string): string {
- let ss = s.split(from);
- let outStr = "";
- ss.forEach( (line) => { outStr += to + line; });
+function replaceAll(s: string, from: string, to: string): string {
+ const ss = s.split(from);
+ let outStr = '';
+ ss.forEach((line) => {
+ outStr += to + line;
+ });
- return outStr.replace( to, ""); // remove 1st 'to' substring
+ // remove 1st 'to' substring
+ return outStr.replace(to, '');
}
-function translateLine( s: string ) : string {
- let line = s;
- line = line.replace( CB_BAD, "TypeScript");
- line = line.replace( CB_OK, "ArkTS");
- //.. |CB_RULE| replace:: Rule
- line = line.replace( CB_ERROR, "**Severity: error**" );
- line = line.replace( CB_WARNING, "**Severity: warning**" );
- line = line.replace(CB_SEE, "## See also" );
+function translateLine(s: string): string {
+ let line = s;
+ line = line.replace(CB_BAD, 'TypeScript');
+ line = line.replace(CB_OK, 'ArkTS');
- line = line.replace( "|JS|", "JavaScript");
- line = line.replace( "|LANG|", "ArkTS"); //.. |LANG| replace:: {lang}
- line = line.replace( "|TS|", "TypeScript");
-
- return line;
-}
+ line = line.replace(CB_ERROR, '**Severity: error**');
+ line = line.replace(CB_WARNING, '**Severity: warning**');
+ line = line.replace(CB_SEE, '## See also');
+ line = replaceAll(line, '|JS|', 'JavaScript');
+ line = replaceAll(line, '|LANG|', 'ArkTS');
+ line = replaceAll(line, '|TS|', 'TypeScript');
-function translateTeg( s: string) :string {
- return replaceAll( s, "\`\`", '"' ).trim();
+ return line;
}
-
-function makeHdr( s: string) :string {
- return replaceAll( s, "\`\`", "\'" );
+function translateTeg(s: string): string {
+ return replaceAll(s, '``', '"').trim();
}
-
-function highlightCode( s: string ): string {
- let ss = s.split("\`\`");
- let line = ss[0];
- for( let i = 1; i < ss.length; i++ ) {
- if( (i % 2) === 0 )
- line += T_END_CODE;
- else
- line += T_CODE;
- line += ss[i];
- }
- return line;
+function highlightCode(s: string): string {
+ const ss = s.split('``');
+ let line = ss[0];
+ for (let i = 1; i < ss.length; i++) {
+ if (i % 2 === 0) {
+ line += T_END_CODE;
+ } else {
+ line += T_CODE;
+ }
+ line += ss[i];
+ }
+ return line;
}
-function escapeSym( s: string ): string {
- let ss = replaceAll(s, "\'", "\\\'");
- return replaceAll(ss, "\"", "\\\"");
+function escapeSym(s: string): string {
+ const ss = replaceAll(s, '\'', '\\\'');
+ return replaceAll(ss, '"', '\\"');
}
-function setNBSP( s: string ): string {
- let ss = "";
- let flag = true;
- for( let ch of s ) {
- if( ch !== " " && ch !== "\t" )
- flag = false;
- if( flag && ch === " " )
- ss += T_NBSP;
- else if( flag && ch ==="\t" )
- ss += T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP ;
- else
- ss += ch;
- }
- return ss;
+function setNBSP(s: string): string {
+ let ss = '';
+ let flag = true;
+ for (const ch of s) {
+ if (ch !== ' ' && ch !== '\t') {
+ flag = false;
+ }
+ if (flag && ch === ' ') {
+ ss += T_NBSP;
+ } else if (flag && ch === '\t') {
+ ss += T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP + T_NBSP;
+ } else {
+ ss += ch;
+ }
+ }
+ return ss;
}
-function skipEmptyLines() {
- while( _line < doc_lines.length ) {
- let s = doc_lines[_line];
- s = s.trim();
- if( s !== "")
- break;
- _line++;
- }
+function skipEmptyLines(): void {
+ while (curLine < docLines.length) {
+ let s = docLines[curLine];
+ s = s.trim();
+ if (s !== '') {
+ break;
+ }
+ curLine++;
+ }
}
function isHeader(): boolean {
- return doc_lines[ _line ].startsWith( CB_ ) || doc_lines[ _line ].startsWith( ".." ) ;
+ return docLines[curLine].startsWith(CB_PREFIX) || docLines[curLine].startsWith('..');
}
-function needHeader() {
- while ( _line < doc_lines.length && !isHeader() )
- _line++;
+function needHeader(): void {
+ while (curLine < docLines.length && !isHeader()) {
+ curLine++;
+ }
}
-
-//
-// parsing functions
-//
-
-function makeRecept() {
- recNum = Number(doc_lines[_line].slice(NEW_REC_HEADER.length, NEW_REC_HEADER.length+3))
- console.log("cookBookMsg[ " + recNum + " ] = " + STR_DLMTR + CL);
- _line++;
- mdText = [];
- makeTeg();
- makeBody();
- makeBad();
- makeOk();
- makeSee();
-
- // emit .md file
- let mdFileName = join("./md", "recipe" + recNum + ".md" );
- writeFileSync( mdFileName, "", { flag: 'w', });
- mdText.forEach((mdLine) => {
-console.error("MD> " + mdLine);
- writeFileSync(mdFileName, mdLine + '\n', { flag: "a+"} )
- });
-
- console.log(STR_DLMTR + ";");
- console.log("");
+function isFixTitle(): boolean {
+ return docLines[curLine].trimStart().startsWith(CB_FIX);
}
+/*
+ *
+ * parsing functions
+ *
+ */
-function makeTeg() {
- needHeader();
-console.error(">>>TEG>>>: " + _line + " -> " + doc_lines[_line]);
- if( ! doc_lines[ _line ].startsWith( CB_R ) )
- return;
- let line = doc_lines[ _line ].split( CB_R )[1];
-
- mdText.push("# " + line); //.split(':')[1] );
- mdText.push("");
+function makeFixTitle(): void {
+ while (curLine < docLines.length && !isHeader() && !isFixTitle()) {
+ curLine++;
+ }
- line = escapeSym( translateLine(line) );
- let teg = translateTeg( line );
- let hdr = highlightCode(line);
- console.log(hdr + T_BR + CL);
- tegs[ recNum ] = teg; //.split(':')[1];
- _line++;
+ if (isFixTitle()) {
+ const title = docLines[curLine].split(CB_FIX)[1].trim();
+ fixTitles.set(recNum, escapeSym(title));
+ }
}
+function makeRecipe(): void {
+ const line = docLines[curLine];
+ recNum = Number(line.replace(/\D/g, ''));
+ console.log('cookBookMsg[ ' + recNum + ' ] = ' + STR_DLMTR + CL);
+ curLine++;
+ mdText = [];
+ makeTag();
+ makeBody();
+ makeBad();
+ makeOk();
+ makeSee();
+
+ // emit .md file
+ const mdFileName = join('./md', 'recipe' + recNum + '.md');
+ writeFileSync(mdFileName, '', { flag: 'w' });
+ mdText.forEach((mdLine) => {
+ console.error('MD> ' + mdLine);
+ writeFileSync(mdFileName, mdLine + '\n', { flag: 'a+' });
+ });
+
+ console.log(STR_DLMTR + ';');
+ console.log('');
+}
-function makeBody(): string {
- let body = "";
-
- needHeader();
-console.error(">>>BODY HDR>>>: " + + _line + " -> " + doc_lines[_line]);
- if( !doc_lines[ _line ].startsWith( CB_RULE ) )
- return "";
+function makeTag(): void {
+ needHeader();
+ console.error('>>>TEG>>>: ' + curLine + ' -> ' + docLines[curLine]);
+ if (!docLines[curLine].startsWith(CB_R)) {
+ return;
+ }
+ let line = docLines[curLine].split(CB_R)[1];
- let line = doc_lines[ _line ].trim();
- let md_line = line;
- line = line.replace( CB_RULE, "");
- line = escapeSym( translateLine(line) );
- tegs[ recNum ] = tegs[ recNum ].trim() + " (" + replaceAll(translateTeg(line), '"', '') + ")";
+ mdText.push('# ' + translateLine(line));
+ mdText.push('');
- _line++; _line++; // skip underline
- console.log( T_HR + T_BOLD + "Rule" + T_END_BOLD + T_BR + CL );
+ line = escapeSym(translateLine(line));
+ const teg = translateTeg(line);
+ const hdr = highlightCode(line);
+ console.log(hdr + T_BR + CL);
+ tegs[recNum] = teg;
+ curLine++;
+}
- mdText.push( md_line.replace( CB_RULE, "Rule" ) ); //("## Rule");
- mdText.push("");
+function makeBody(): string {
+ let body = '';
+ needHeader();
+ console.error('>>>BODY HDR>>>: ' + +curLine + ' -> ' + docLines[curLine]);
+ if (!docLines[curLine].startsWith(CB_RULE)) {
+ return '';
+ }
+ let line = docLines[curLine].trim();
+ const mdLine = line;
+ line = line.replace(CB_RULE, '');
+ line = escapeSym(translateLine(line));
+ tegs[recNum] = tegs[recNum].trim() + ' (' + replaceAll(translateTeg(line), '"', '') + ')';
+ curLine++;
+
+ // skip underline
+ curLine++;
+ console.log(T_HR + T_BOLD + 'Rule' + T_END_BOLD + T_BR + CL);
+
+ // ("## Rule");
+ mdText.push(mdLine.replace(CB_RULE, 'Rule'));
+ mdText.push('');
+ needHeader();
+ console.error('>>>BODY 2 HDR>>>: ' + +curLine + ' -> ' + docLines[curLine]);
+
+ if (docLines[curLine].startsWith(CB_META)) {
+ curLine++;
+ makeFixTitle();
needHeader();
-console.error(">>>BODY 2 HDR>>>: " + + _line + " -> " + doc_lines[_line]);
- if( doc_lines[ _line ].startsWith(CB_META) ) {
- _line++;
- needHeader();
-console.error(">>>BODY 3 HDR>>>: " + + _line + " -> " + doc_lines[_line]);
- }
- //_line++;
- while( !isHeader() || doc_lines[ _line ].startsWith( CB_ERROR ) || doc_lines[ _line ].startsWith( CB_WARNING ) ) {
- let s = translateLine( doc_lines[_line] );
+ console.error('>>>BODY 3 HDR>>>: ' + +curLine + ' -> ' + docLines[curLine]);
+ }
- mdText.push(s);
+ while (!isHeader() || docLines[curLine].startsWith(CB_ERROR) || docLines[curLine].startsWith(CB_WARNING)) {
+ let s = translateLine(docLines[curLine]);
- s = highlightCode( s );
- s = escapeSym( s );
- console.log(s + CL);
-
- body += s;
- _line++;
- }
- console.log(T_BR + CL);
+ mdText.push(s);
+ s = highlightCode(s);
+ s = escapeSym(s);
+ console.log(s + CL);
+ body += s;
+ curLine++;
+ }
- mdText.push("");
+ console.log(T_BR + CL);
+ mdText.push('');
- return body;
+ return body;
}
+function makeBad(): string {
+ let badCode = '';
+ needHeader();
+ console.error('>>>makeBAD HDR>>>: ' + docLines[curLine]);
+ if (!docLines[curLine].startsWith(CB_BAD)) {
+ return '';
+ }
+ curLine++;
+ // skip underline
+ curLine++;
-function makeBad(): string {
- let badCode ="";
-
- needHeader();
-console.error(">>>makeBAD HDR>>>: " + doc_lines[_line]);
- if( ! doc_lines[_line].startsWith( CB_BAD ) ) {
- return "";
- }
- _line++; _line++; // skip underline
+ console.log(T_HR + T_BOLD + 'TypeScript' + T_END_BOLD + T_BR + CL);
- console.log( T_HR + T_BOLD + "TypeScript" + T_END_BOLD + T_BR + CL );
+ mdText.push('## TypeScript');
+ mdText.push('');
- mdText.push("## TypeScript");
- mdText.push("");
+ while (curLine < docLines.length && !isHeader()) {
+ let s = translateLine(docLines[curLine]);
+ mdText.push(s);
- while( _line < doc_lines.length && !isHeader() ) {
- let s = translateLine( doc_lines[_line] );
- mdText.push( s );
+ s = highlightCode(s);
+ console.log(s + CL);
- s = highlightCode( s );
- console.log(s + CL);
+ badCode += s;
+ curLine++;
+ }
- badCode += s;
- _line++;
+ skipEmptyLines();
+ if (docLines[curLine++].startsWith(CODE_BLOCK)) {
+ mdText.push('```');
+ console.log(T_CODE + CL);
+ while (curLine < docLines.length && !isHeader()) {
+ mdText.push(docLines[curLine]);
+ console.log(setNBSP(escapeSym(docLines[curLine])) + T_BR + CL);
+ curLine++;
}
+ console.log(T_END_CODE + T_BR + CL);
- skipEmptyLines();
- if( doc_lines[_line++].startsWith( CODE_BLOCK ) ) {
- mdText.push("```");
- console.log( T_CODE + CL );
- while( _line < doc_lines.length && !isHeader() ) {
- mdText.push( doc_lines[_line] );
- console.log( setNBSP( escapeSym(doc_lines[_line]) ) + T_BR + CL );
- _line++;
- }
- console.log( T_END_CODE + T_BR + CL );
-
- mdText.push("```");
- }
- mdText.push("");
-
- return badCode;
-}
+ mdText.push('```');
+ }
+ mdText.push('');
+ return badCode;
+}
function makeOk(): string {
- let goodCode = "";
+ let goodCode = '';
- needHeader();
-console.error( ">>>makeOK HDR>>>: " + doc_lines[ _line ] );
- if( _line >= doc_lines.length || !doc_lines[_line].startsWith(CB_OK) ) {
- return "";
- }
- _line++; _line++; // skip underline
- console.log( T_HR + T_BOLD + "ArkTS" + T_END_BOLD + T_BR + CL );
-
- mdText.push("## ArkTS");
- mdText.push("");
-
- while( _line < doc_lines.length && !isHeader() ) {
- let s = translateLine( doc_lines[ _line ] );
-
- mdText.push( s );
-
- s = highlightCode( s );
- console.log(s + CL);
-
- goodCode += s;
- _line++;
- }
+ needHeader();
+ console.error('>>>makeOK HDR>>>: ' + docLines[curLine]);
+ if (curLine >= docLines.length || !docLines[curLine].startsWith(CB_OK)) {
+ return '';
+ }
+ curLine++;
+ // skip underline
+ curLine++;
+ console.log(T_HR + T_BOLD + 'ArkTS' + T_END_BOLD + T_BR + CL);
- skipEmptyLines();
- if( doc_lines[ _line++ ].startsWith( CODE_BLOCK ) ) {
- console.log( T_CODE + CL );
+ mdText.push('## ArkTS');
+ mdText.push('');
- mdText.push("```");
+ while (curLine < docLines.length && !isHeader()) {
+ let s = translateLine(docLines[curLine]);
- while( _line < doc_lines.length && !isHeader() ) {
- mdText.push( doc_lines[_line] );
- console.log( setNBSP( escapeSym(doc_lines[ _line ]) ) + T_BR + CL );
- _line++;
- }
- console.log( T_END_CODE + T_BR + CL);
+ mdText.push(s);
- mdText.push("```");
- }
+ s = highlightCode(s);
+ console.log(s + CL);
- mdText.push("");
+ goodCode += s;
+ curLine++;
+ }
- return goodCode;
-}
+ skipEmptyLines();
+ if (docLines[curLine++].startsWith(CODE_BLOCK)) {
+ console.log(T_CODE + CL);
+ mdText.push('```');
-function makeSee( ): string {
- const RECIPE = "Recipe ";
-console.error(">>> #" + recNum + " PASSED: " + doc_lines[_line]);
- while( _line < doc_lines.length && !doc_lines[ _line ].startsWith( ".." ) ) {
+ while (curLine < docLines.length && !isHeader()) {
+ mdText.push(docLines[curLine]);
+ console.log(setNBSP(escapeSym(docLines[curLine])) + T_BR + CL);
+ curLine++;
+ }
+ console.log(T_END_CODE + T_BR + CL);
- let s = translateLine( doc_lines[_line] );
+ mdText.push('```');
+ }
- if( s.split(CB_REF)[1] ) {
- s = s.replace("*", "-")
- s = s.replace( CB_REF, RECIPE);
- s = s.replace("`R", "");
- let ruleNum = Number( s.replace("`", "").split(RECIPE)[1]);
-console.error(">>>RULE in SEE " + ruleNum + " " + s.replace("`", "") + " -> " + ruleNames[ruleNum] );
- s = s.replace("`", ":");
- s += ' ' + ruleNames[ruleNum];
- }
+ mdText.push('');
- mdText.push( s );
+ return goodCode;
+}
- if( doc_lines[_line].startsWith(CB_SEE) )
- _line++;
- _line++;
- }
+function makeSee(): string {
+ const RECIPE = 'Recipe ';
+ console.error('>>> #' + recNum + ' PASSED: ' + docLines[curLine]);
+ while (curLine < docLines.length && !docLines[curLine].startsWith('..')) {
+ let s = translateLine(docLines[curLine]);
+
+ if (s.split(CB_REF)[1]) {
+ s = s.replace('*', '-');
+ s = s.replace(CB_REF, RECIPE);
+ s = s.replace('`R', '');
+ const ruleNum = Number(s.replace('`', '').split(RECIPE)[1]);
+ console.error('>>>RULE in SEE ' + ruleNum + ' ' + s.replace('`', '') + ' -> ' + ruleNames[ruleNum]);
+ s = s.replace('`', ':');
+ s += ' ' + ruleNames[ruleNum];
+ }
- mdText.push("");
+ mdText.push(s);
- return "";
-}
+ if (docLines[curLine].startsWith(CB_SEE)) {
+ curLine++;
+ }
+ curLine++;
+ }
+
+ mdText.push('');
+ return '';
+}
-//
-// Main routine
-//
+/*
+ *
+ * Main routine
+ *
+ */
let commandLineArgs = process.argv.slice(2);
if (commandLineArgs.length === 0) {
- console.error(">>> Command line error: no arguments");
- process.exit(-1);
+ console.error('>>> Command line error: no arguments');
+ process.exit(-1);
}
-if( commandLineArgs[0] == '-md') {
- commandLineArgs = process.argv.slice(3);
- MAKE_MD = true;
+if (commandLineArgs[0] === '-md') {
+ commandLineArgs = process.argv.slice(3);
}
-let inFileName = commandLineArgs[0];
+const inFileName = commandLineArgs[0];
console.log(COPYRIGHT_HEADER);
-console.log( CODE_PROLOGUE );
-syncReadFile( inFileName);
+console.log(CODE_PROLOGUE);
+syncReadFile(inFileName);
-for( recNum = 1; recNum < tegs.length; recNum++ ) {
- console.log( "cookBookTag[ " + recNum + " ] = " + STR_DLMTR + ( tegs[ recNum ] ? tegs[ recNum ] : "" ) + STR_DLMTR + ";" );
+for (recNum = 1; recNum < tegs.length; recNum++) {
+ console.log('cookBookTag[ ' + recNum + ' ] = ' + STR_DLMTR + (tegs[recNum] ? tegs[recNum] : '') + STR_DLMTR + ';');
}
+console.log('\nexport const cookBookRefToFixTitle: Map = new Map([');
+for (const num of fixTitles.keys()) {
+ console.log(` [${num}, '${fixTitles.get(num)}'],`);
+}
+console.log(']);');
diff --git a/ets2panda/linter-4.2/docs/rules/recipe118.md b/ets2panda/linter-4.2/docs/rules/recipe118.md
deleted file mode 100644
index f625bc3e982353617c9dd28f7c6d67ff886e1e6d..0000000000000000000000000000000000000000
--- a/ets2panda/linter-4.2/docs/rules/recipe118.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# Special import type declarations are not supported
-
-Rule ``arkts-no-special-imports``
-
-**Severity: error**
-
-ArkTS does not have a special notation for importing types.
-Use ordinary import instead.
-
-
-## TypeScript
-
-
-```
-
- // Re-using the same import
- import { APIResponseType } from "api"
-
- // Explicitly use import type
- import type { APIResponseType } from "api"
-
-```
-
-## ArkTS
-
-
-```
-
- import { APIResponseType } from "api"
-
-```
-
-## See also
-
-- 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``)
-
-
diff --git a/ets2panda/linter-4.2/docs/rules/recipe127.md b/ets2panda/linter-4.2/docs/rules/recipe127.md
deleted file mode 100644
index c89d24fbac4b50b1e68937ea99e7e979ae8c1452..0000000000000000000000000000000000000000
--- a/ets2panda/linter-4.2/docs/rules/recipe127.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Special ``export type`` declarations are not supported
-
-Rule ``arkts-no-special-exports``
-
-**Severity: error**
-
-ArkTS does not have a special notation for exporting types through
-``export type ...``. Use ordinary export instead.
-
-
-## TypeScript
-
-
-```
-
- // Explicitly exported class:
- export class Class1 {
- // ...
- }
-
- // Declared class later exported through export type ...
- class Class2 {
- // ...
- }
-
- // This is not supported:
- export type { Class2 }
-
-```
-
-## ArkTS
-
-
-```
-
- // Explicitly exported class:
- export class Class1 {
- // ...
- }
-
- // Explicitly exported class:
- export class Class2 {
- // ...
- }
-
-```
-
-
diff --git a/ets2panda/linter-4.2/docs/rules/recipe144.md b/ets2panda/linter-4.2/docs/rules/recipe144.md
index 28acf4a49c2d12c132c77f1e840a1d5de12e0f0d..ec36a4ffbf2f7a833502932ffc2010f44f6e2d24 100644
--- a/ets2panda/linter-4.2/docs/rules/recipe144.md
+++ b/ets2panda/linter-4.2/docs/rules/recipe144.md
@@ -29,8 +29,6 @@ Properties and functions of the global object: ``eval``
``handler.has()``, ``handler.isExtensible()``, ``handler.ownKeys()``,
``handler.preventExtensions()``, ``handler.set()``, ``handler.setPrototypeOf()``
-``ArrayBuffer``: ``isView``
-
## See also
diff --git a/ets2panda/linter-4.2/docs/rules/recipe151.md b/ets2panda/linter-4.2/docs/rules/recipe151.md
index 0b00ba64328b8da460cba56c63a6dd63fd050e23..2e7fd2d46a7c1a86d1c9ac02edd38945f5cfd0b1 100644
--- a/ets2panda/linter-4.2/docs/rules/recipe151.md
+++ b/ets2panda/linter-4.2/docs/rules/recipe151.md
@@ -1,17 +1,18 @@
# Usage of ``ESObject`` type is restricted
-Rule ``arkts-limited-esobject``
+Rule ``arkts-limited-esobj``
**Severity: warning**
-ArkTS does not allow using ``ESObject`` type in some cases. The most part of limitations
-are put in place in order to prevent spread of dynamic objects in the static codebase.
-The only scenario where it is permited to use ``ESObject`` as type specifier is in local
-variable declaration. Initialization of variables with ``ESObject`` type is also limited.
-Such variables can only be initialized with values that originate from interop:
-other ``ESObject`` typed variables, any, unknown, variables with anonymous type, etc.
-It is prohibited to initialize ``ESObject`` typed variable with statically typed value.
-Varaible of type ``ESObject`` can only be passed to interop calls and assigned to other
+ArkTS does not allow using ``ESObject`` type in some cases. The most part of
+limitations are put in place in order to prevent spread of dynamic objects in
+the static codebase. The only scenario where it is permited to use ``ESObject``
+as type specifier is in local variable declaration. Initialization of variables
+with ``ESObject`` type is also limited. Such variables can only be initialized
+with values that originate from interop: other ``ESObject`` typed variables,
+any, unknown, variables with anonymous type, etc. It is prohibited to
+initialize ``ESObject`` typed variable with statically typed value. Varaible
+of type ``ESObject`` can only be passed to interop calls and assigned to other
variables of type ``ESObject``.
@@ -32,12 +33,13 @@ variables of type ``ESObject``.
let e3: ESObject = {}; // CTE - can't initialize ESObject with not dynamic values
let e4: ESObject = []; // CTE - can't initialize ESObject with not dynamic values
let e5: ESObject = ""; // CTE - can't initialize ESObject with not dynamic values
+ e5['prop'] // CTE - can't access dynamic properties of ESObject
+ e5[1] // CTE - can't access dynamic properties of ESObject
+ e5.prop // CTE - can't access dynamic properties of ESObject
+
let e6: ESObject = foo(); // OK - explicitly annotaded as ESObject
let e7 = e6; // OK - initialize ESObject with ESObject
- e6['prop'] // CTE - can't access dynamic properties of ESObject
- e6[1] // CTE - can't access dynamic properties of ESObject
- e6.prop // CTE - can't access dynamic properties of ESObject
- bar(e6) // OK - ESObject is passed to interop call
+ bar(e7) // OK - ESObject is passed to interop call
}
```
@@ -51,4 +53,3 @@ variables of type ``ESObject``.
- Recipe 066: ``in`` operator is not supported (``arkts-no-in``)
- Recipe 137: ``globalThis`` is not supported (``arkts-no-globalthis``)
-
diff --git a/ets2panda/linter-4.2/docs/rules/recipe29.md b/ets2panda/linter-4.2/docs/rules/recipe29.md
index c7be978d6962ce635842df26f13c20591f59b060..7af61c43e6cc5976617e17be86598978444d8ebe 100644
--- a/ets2panda/linter-4.2/docs/rules/recipe29.md
+++ b/ets2panda/linter-4.2/docs/rules/recipe29.md
@@ -10,9 +10,13 @@ that are either declared in the class, or accessible via inheritance. Accessing
any other fields is prohibited, and causes compile-time errors.
To access a field, use ``obj.field`` syntax, indexed access (``obj["field"]``)
-is not supported. An exception are all typed arrays from the standard library
-(for example, ``Int32Array``), which support access to their elements through
-``container[index]`` syntax.
+is not supported. An exception are:
+
+- All typed arrays from the standard library (for example, ``Int32Array``), which
+support access to their elements through ``container[index]`` syntax.
+- Tuples.
+- Records.
+- Enums.
## TypeScript
diff --git a/ets2panda/linter-4.2/package.json b/ets2panda/linter-4.2/package.json
index 651fc138f3956a2c3dcb3f761dc83b75fce18deb..2db6b27776ac981d55bc9639fb34515ad4f36064 100644
--- a/ets2panda/linter-4.2/package.json
+++ b/ets2panda/linter-4.2/package.json
@@ -11,16 +11,17 @@
"scripts": {
"tsc": "tsc",
"tscv": "tsc --version",
- "webpack":"webpack -t node --config webpack.config.js",
+ "webpack": "webpack -t node --config webpack.config.js",
"clean": "rimraf build dist bundle",
"compile": "npm run tsc",
"build": "npm run clean && npm run compile && npm run webpack && npm run pack:linter",
"postinstall": "npm run build",
"pack:linter": "rimraf bundle && mkdir bundle && npm pack --pack-destination bundle",
- "test": "npm run compile && rimraf test/results test_rules/results && node build/src/TestRunner.js test test_rules",
+ "test": "npm run test_main && npm run test_rules && npm run test_regression",
"test_main": "npm run compile && rimraf test/results && node build/src/TestRunner.js test",
+ "test_regression": "npm run compile && rimraf test_regression/results && node build/src/TestRunner.js test_regression",
"test_rules": "npm run compile && rimraf test_rules/results && node build/src/TestRunner.js test_rules",
- "update-tests": "node scripts/update-test-results.mjs test test_rules"
+ "update-tests": "node scripts/update-test-results.mjs test test_rules test_regression"
},
"devDependencies": {
"@types/node": "18.11.7",
@@ -39,7 +40,6 @@
"log4js": "6.4.0",
"commander": "9.4.0"
},
-
"bundledDependencies": [
"typescript",
"log4js",
diff --git a/ets2panda/linter-4.2/scripts/update-test-results.mjs b/ets2panda/linter-4.2/scripts/update-test-results.mjs
index dc579e42453a4416215504af7d0e3133221a4f33..45029c37eb3f2858d3a3848b5eebc2453598d268 100644
--- a/ets2panda/linter-4.2/scripts/update-test-results.mjs
+++ b/ets2panda/linter-4.2/scripts/update-test-results.mjs
@@ -37,7 +37,7 @@ const RESULTS_DIR = 'results'
let testDirs = [];
// forces to update all tests regardless of whether there was diff in a test result
-const force_update = false;
+let force_update = false;
for (let arg of process.argv.slice(2)) {
if (arg === '--force')
@@ -72,33 +72,37 @@ function readTestFile(filePath) {
function updateTest(testDir, testFile, mode) {
let resultExt = RESULT_EXT[mode];
- let testFileWithExt = testFile + resultExt;
+ let resultFileWithExt = testFile + resultExt;
+ let resultFilePath = path.join(testDir, resultFileWithExt);
// Do not update autofix result if test is skipped
- if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFileWithExt + AUTOFIX_SKIP_EXT))) {
+ if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFile + AUTOFIX_SKIP_EXT))) {
return;
}
- // Update test result when 'diff' exists or the 'force' option is enabled.
- if (!fs.existsSync(path.join(testDir, RESULTS_DIR, testFileWithExt + DIFF_EXT)) && !force_update) {
+ // Update test result when:
+ // - '.diff' exists
+ // - expected '.json' doesn't exist
+ // - 'force' option is enabled
+ if (fs.existsSync(resultFilePath) && !fs.existsSync(path.join(testDir, RESULTS_DIR, resultFileWithExt + DIFF_EXT)) && !force_update) {
return;
}
- let expectedResult = readTestFile(path.join(testDir, testFileWithExt));
+ let expectedResult = readTestFile(resultFilePath);
const copyright = expectedResult?.copyright ?? DEFAULT_COPYRIGHT;
- let actualResult = readTestFile(path.join(testDir, RESULTS_DIR, testFileWithExt));
+ let actualResult = readTestFile(path.join(testDir, RESULTS_DIR, resultFileWithExt));
if (!actualResult || !actualResult.nodes) {
- console.log(`Failed to update ${testFileWithExt}: couldn't read ACTUAL result file.`);
+ console.log(`Failed to update ${resultFileWithExt}: couldn't read ACTUAL result file.`);
return;
}
// Write file with actual test results.
let newResultJSON = JSON.stringify({ copyright, nodes: actualResult.nodes }, null, 4);
- fs.writeFileSync(path.join(testDir, testFileWithExt), newResultJSON);
+ fs.writeFileSync(resultFilePath, newResultJSON);
- console.log(`Updated ${testFileWithExt}`);
+ console.log(`Updated ${resultFileWithExt}`);
}
for (let testDir of testDirs) {
diff --git a/ets2panda/linter-4.2/src/AutofixInfo.ts b/ets2panda/linter-4.2/src/AutofixInfo.ts
index a0d25dbd25e6e8c99c812ba2d08bcb6f80ed6a06..6265e48de896b3350720261bd036f9419eceb091 100644
--- a/ets2panda/linter-4.2/src/AutofixInfo.ts
+++ b/ets2panda/linter-4.2/src/AutofixInfo.ts
@@ -17,4 +17,4 @@ export interface AutofixInfo {
problemID: string;
start: number;
end: number;
-}
\ No newline at end of file
+}
diff --git a/ets2panda/linter-4.2/src/Autofixer.ts b/ets2panda/linter-4.2/src/Autofixer.ts
index d082f5ade8234e5feb0f20069176f9e16bd2b751..6bfe2e8c26f6549f47970f756f933e812969d283 100644
--- a/ets2panda/linter-4.2/src/Autofixer.ts
+++ b/ets2panda/linter-4.2/src/Autofixer.ts
@@ -14,20 +14,24 @@
*/
import * as ts from 'typescript';
-import { AutofixInfo } from './AutofixInfo';
+import type { AutofixInfo } from './AutofixInfo';
import { FaultID } from './Problems';
import { isAssignmentOperator } from './Utils';
export const AUTOFIX_ALL: AutofixInfo = {
- problemID: '', start: -1, end: -1
-}
+ problemID: '',
+ start: -1,
+ end: -1
+};
-// Some fixes are potentially risky and may break source code if fixes
-// are applied separately.
-// Temporary solution is to disable all risky autofixes, until the
-// algorithm is improved to guarantee that fixes can be applied
-// safely and won't break program code.
-const UNSAFE_FIXES: FaultID[] = [ FaultID.LiteralAsPropertyName, FaultID.PropertyAccessByIndex ];
+/*
+ * Some fixes are potentially risky and may break source code if fixes
+ * are applied separately.
+ * Temporary solution is to disable all risky autofixes, until the
+ * algorithm is improved to guarantee that fixes can be applied
+ * safely and won't break program code.
+ */
+const UNSAFE_FIXES: FaultID[] = [FaultID.LiteralAsPropertyName, FaultID.PropertyAccessByIndex];
export interface Autofix {
replacementText: string;
@@ -36,149 +40,180 @@ export interface Autofix {
}
export class AutofixInfoSet {
- private autofixInfo: AutofixInfo[];
+ private readonly autofixInfo: AutofixInfo[];
constructor(autofixInfo: AutofixInfo[] | undefined) {
this.autofixInfo = autofixInfo ? autofixInfo : [];
}
- public shouldAutofix(node: ts.Node, faultID: FaultID): boolean {
- if (UNSAFE_FIXES.includes(faultID)) return false;
- if (this.autofixInfo.length === 0) return false;
+ shouldAutofix(node: ts.Node, faultID: FaultID): boolean {
+ if (UNSAFE_FIXES.includes(faultID)) {
+ return false;
+ }
+ 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;
+ return (
+ this.autofixInfo.findIndex((value) => {
+ return value.start === node.getStart() && value.end === node.getEnd() && value.problemID === FaultID[faultID];
+ }) !== -1
+ );
}
}
export function fixLiteralAsPropertyName(node: ts.Node): Autofix[] | undefined {
if (ts.isPropertyDeclaration(node) || ts.isPropertyAssignment(node)) {
- let propName = (node as (ts.PropertyDeclaration | ts.PropertyAssignment)).name;
- let identName = propertyName2IdentifierName(propName);
- if (identName)
+ const propName = node.name;
+ const identName = propertyName2IdentifierName(propName);
+ if (identName) {
return [{ replacementText: identName, start: propName.getStart(), end: propName.getEnd() }];
+ }
}
return undefined;
}
export function fixPropertyAccessByIndex(node: ts.Node): Autofix[] | undefined {
if (ts.isElementAccessExpression(node)) {
- let elemAccess = node as ts.ElementAccessExpression;
- let identifierName = indexExpr2IdentifierName(elemAccess.argumentExpression);
- if (identifierName)
- return [{
- replacementText: elemAccess.expression.getText() + '.' + identifierName,
- start: elemAccess.getStart(), end: elemAccess.getEnd()
- }];
+ const elemAccess = node;
+ const identifierName = indexExpr2IdentifierName(elemAccess.argumentExpression);
+ if (identifierName) {
+ return [
+ {
+ replacementText: elemAccess.expression.getText() + '.' + identifierName,
+ start: elemAccess.getStart(),
+ end: elemAccess.getEnd()
+ }
+ ];
+ }
}
return undefined;
}
-export function fixFunctionExpression(funcExpr: ts.FunctionExpression,
+export function fixFunctionExpression(
+ funcExpr: ts.FunctionExpression,
params: ts.NodeArray = funcExpr.parameters,
retType: ts.TypeNode | undefined = funcExpr.type,
- modifiers: readonly ts.Modifier[] | undefined): Autofix {
+ modifiers: readonly ts.Modifier[] | undefined
+): Autofix {
let arrowFunc: ts.Expression = ts.factory.createArrowFunction(
- modifiers, undefined, params, retType, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
+ modifiers,
+ undefined,
+ params,
+ retType,
+ ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
funcExpr.body
);
if (needsParentheses(funcExpr)) {
arrowFunc = ts.factory.createParenthesizedExpression(arrowFunc);
}
- let text = printer.printNode(ts.EmitHint.Unspecified, arrowFunc, funcExpr.getSourceFile());
+ const text = printer.printNode(ts.EmitHint.Unspecified, arrowFunc, funcExpr.getSourceFile());
return { start: funcExpr.getStart(), end: funcExpr.getEnd(), replacementText: text };
}
export function fixReturnType(funcLikeDecl: ts.FunctionLikeDeclaration, typeNode: ts.TypeNode): Autofix {
- let text = ': ' + printer.printNode(ts.EmitHint.Unspecified, typeNode, funcLikeDecl.getSourceFile());
- let pos = getReturnTypePosition(funcLikeDecl);
+ const text = ': ' + printer.printNode(ts.EmitHint.Unspecified, typeNode, funcLikeDecl.getSourceFile());
+ const pos = getReturnTypePosition(funcLikeDecl);
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};
+ const newVarDecl = ts.factory.createVariableDeclaration(varDecl.name, undefined, undefined, undefined);
+ const text = printer.printNode(ts.EmitHint.Unspecified, newVarDecl, varDecl.getSourceFile());
+ return { start: varDecl.getStart(), end: varDecl.getEnd(), replacementText: text };
}
-export function dropTypeOnlyFlag(
- impExpNode: ts.ImportClause | ts.ExportDeclaration
-): Autofix {
+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);
+ const 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);
+ } else {
+ const 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());
+export function fixDefaultImport(
+ importClause: ts.ImportClause,
+ defaultSpec: ts.ImportSpecifier,
+ nonDefaultSpecs: ts.ImportSpecifier[]
+): Autofix {
+ const nameBindings = nonDefaultSpecs.length > 0 ? ts.factory.createNamedImports(nonDefaultSpecs) : undefined;
+ const newImportClause = ts.factory.createImportClause(importClause.isTypeOnly, defaultSpec.name, nameBindings);
+ const 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());
+ const text = printer.printNode(ts.EmitHint.Unspecified, asExpr, typeAssertion.getSourceFile());
return { start: typeAssertion.getStart(), end: typeAssertion.getEnd(), replacementText: text };
}
const printer: ts.Printer = ts.createPrinter();
-function numericLiteral2IdentifierName(numeric: ts.NumericLiteral) {
+function numericLiteral2IdentifierName(numeric: ts.NumericLiteral): string {
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 stringLiteral2IdentifierName(str: ts.StringLiteral): string {
+ const text = str.getText();
+ // cut out starting and ending quoters.
+ return text.substring(1, text.length - 1);
}
function propertyName2IdentifierName(name: ts.PropertyName): string {
- if (name.kind === ts.SyntaxKind.NumericLiteral)
- return numericLiteral2IdentifierName(name as ts.NumericLiteral);
+ if (name.kind === ts.SyntaxKind.NumericLiteral) {
+ return numericLiteral2IdentifierName(name);
+ }
- if (name.kind === ts.SyntaxKind.StringLiteral)
- return stringLiteral2IdentifierName(name as ts.StringLiteral);
+ if (name.kind === ts.SyntaxKind.StringLiteral) {
+ return stringLiteral2IdentifierName(name);
+ }
return '';
}
-function indexExpr2IdentifierName(index: ts.Expression) {
- if (index.kind === ts.SyntaxKind.NumericLiteral)
+function indexExpr2IdentifierName(index: ts.Expression): string {
+ if (index.kind === ts.SyntaxKind.NumericLiteral) {
return numericLiteral2IdentifierName(index as ts.NumericLiteral);
+ }
- if (index.kind === ts.SyntaxKind.StringLiteral)
+ 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.
- // After that, iterate over child nodes in reverse order, until found
- // first closing parenthesis.
- let postParametersPosition = ts.isArrowFunction(funcLikeDecl)
- ? funcLikeDecl.equalsGreaterThanToken.getStart()
- : funcLikeDecl.body.getStart();
+
+ /*
+ * Find position of the first node or token that follows parameters.
+ * After that, iterate over child nodes in reverse order, until found
+ * first closing parenthesis.
+ */
+ const postParametersPosition = ts.isArrowFunction(funcLikeDecl) ?
+ funcLikeDecl.equalsGreaterThanToken.getStart() :
+ funcLikeDecl.body.getStart();
const children = funcLikeDecl.getChildren();
for (let i = children.length - 1; i >= 0; i--) {
const child = children[i];
- if (child.kind === ts.SyntaxKind.CloseParenToken && child.getEnd() < postParametersPosition)
+ if (child.kind === ts.SyntaxKind.CloseParenToken && child.getEnd() < postParametersPosition) {
return child.getEnd();
+ }
}
}
@@ -188,9 +223,15 @@ function getReturnTypePosition(funcLikeDecl: ts.FunctionLikeDeclaration): number
function needsParentheses(node: ts.FunctionExpression): boolean {
const parent = node.parent;
- return ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent) ||
- ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent) ||
- ts.isTypeOfExpression(parent) || ts.isVoidExpression(parent) || ts.isAwaitExpression(parent) ||
- (ts.isCallExpression(parent) && node === parent.expression) ||
- (ts.isBinaryExpression(parent) && !isAssignmentOperator(parent.operatorToken));
-}
\ No newline at end of file
+ return (
+ ts.isPrefixUnaryExpression(parent) ||
+ ts.isPostfixUnaryExpression(parent) ||
+ ts.isPropertyAccessExpression(parent) ||
+ ts.isElementAccessExpression(parent) ||
+ ts.isTypeOfExpression(parent) ||
+ ts.isVoidExpression(parent) ||
+ ts.isAwaitExpression(parent) ||
+ ts.isCallExpression(parent) && node === parent.expression ||
+ ts.isBinaryExpression(parent) && !isAssignmentOperator(parent.operatorToken)
+ );
+}
diff --git a/ets2panda/linter-4.2/src/CommandLineOptions.ts b/ets2panda/linter-4.2/src/CommandLineOptions.ts
index c9803afb790b06c472f08042ff0054a3ffdcd97e..9dd4acd716855a7e1425894afb585d4c2c015b50 100644
--- a/ets2panda/linter-4.2/src/CommandLineOptions.ts
+++ b/ets2panda/linter-4.2/src/CommandLineOptions.ts
@@ -13,8 +13,8 @@
* limitations under the License.
*/
-import * as ts from 'typescript';
-import { AutofixInfo } from './AutofixInfo';
+import type * as ts from 'typescript';
+import type { AutofixInfo } from './AutofixInfo';
export interface CommandLineOptions {
testMode?: boolean;
@@ -25,4 +25,4 @@ export interface CommandLineOptions {
parsedConfigFile?: ts.ParsedCommandLine;
inputFiles: string[];
autofixInfo?: AutofixInfo[];
-}
\ No newline at end of file
+}
diff --git a/ets2panda/linter-4.2/src/CommandLineParser.ts b/ets2panda/linter-4.2/src/CommandLineParser.ts
index 9544757307dc76c9897aaa57ee30fc7f79c45f04..007bf9a175030fd6c2af70d9bf8e65a7e30d8285 100644
--- a/ets2panda/linter-4.2/src/CommandLineParser.ts
+++ b/ets2panda/linter-4.2/src/CommandLineParser.ts
@@ -13,9 +13,9 @@
* limitations under the License.
*/
-import Logger from '../utils/logger';
+import logger from '../utils/logger';
import { logTscDiagnostic, decodeAutofixInfo } from './Utils';
-import { CommandLineOptions } from './CommandLineOptions';
+import type { CommandLineOptions } from './CommandLineOptions';
import { AUTOFIX_ALL } from './Autofixer';
import { Command, Option } from 'commander';
import * as ts from 'typescript';
@@ -27,15 +27,16 @@ const TSX_EXT = '.tsx';
const ETS_EXT = '.ets';
const JSON_EXT = '.json';
-const logger = Logger.getLogger();
+const loggerInstance = logger.getLogger();
let inputFiles: string[];
let responseFile = '';
-function addSrcFile(value: string, dummy: string) {
- if(value.startsWith('@'))
+function addSrcFile(value: string): void {
+ if (value.startsWith('@')) {
responseFile = value;
- else
+ } else {
inputFiles.push(value);
+ }
}
const getFiles = (dir: string): string[] => {
@@ -43,87 +44,114 @@ const getFiles = (dir: string): string[] => {
const files = fs.readdirSync(dir);
for (let i = 0; i < files.length; ++i) {
- let name = path.join(dir, files[i]);
+ const name = path.join(dir, files[i]);
if (fs.statSync(name).isDirectory()) {
resultFiles.push(...getFiles(name));
} else {
- let extension = path.extname(name);
- if (extension === TS_EXT || extension === TSX_EXT || extension === ETS_EXT)
+ const extension = path.extname(name);
+ if (extension === TS_EXT || extension === TSX_EXT || extension === ETS_EXT) {
resultFiles.push(name);
+ }
}
}
return resultFiles;
};
-function addProjectFolder(projectFolder: string, previous: any ) {
+function addProjectFolder(projectFolder: string, previous: string[]): string[] {
return previous.concat([projectFolder]);
}
-export function parseCommandLine(commandLineArgs: string[]): CommandLineOptions {
+function formCommandLineOptions(program: Command): CommandLineOptions {
const opts: CommandLineOptions = { inputFiles: [], warningsAsErrors: false };
+ // Default mode of the linter.
+ opts.strictMode = true;
+ opts.inputFiles = inputFiles;
+
+ const options = program.opts();
+ if (options.relax) {
+ opts.strictMode = false;
+ }
+ if (options.TSC_Errors) {
+ opts.logTscErrors = true;
+ }
+ if (options.devecoPluginMode) {
+ opts.ideMode = true;
+ }
+ if (options.testMode) {
+ opts.testMode = true;
+ }
+ if (options.projectFolder) {
+ doProjectFolderArg(options.projectFolder, opts);
+ }
+ if (options.project) {
+ doProjectArg(options.project, opts);
+ }
+ if (options.autofix) {
+ doAutofixArg(options.autofix, opts);
+ }
+ if (options.warningsAsErrors) {
+ opts.warningsAsErrors = true;
+ }
+ return opts;
+}
+export function parseCommandLine(commandLineArgs: string[]): CommandLineOptions {
const program = new Command();
- program
- .name('tslinter')
- .description('Linter for TypeScript sources')
- .version('0.0.1');
- program
- .option('-E, --TSC_Errors', 'show error messages from Tsc')
- .option('--relax', 'relax mode On')
- .option('--test-mode', 'run linter as if running TS test files')
- .option('--deveco-plugin-mode', 'run as IDE plugin')
- .option('-p, --project ', 'path to TS project config file')
- .option('--project-folder ', 'path to folder containig TS files to verify', addProjectFolder, [])
- .option('--autofix [autofix.json]', 'fix errors specified by JSON file (all if file is omitted)',
- (val: string, prev: string|boolean) => { return val.endsWith(JSON_EXT) ? val : true; })
- .addOption(new Option('--warnings-as-errors', 'treat warnings as errors').hideHelp(true));
- program
- .argument('[srcFile...]', 'files to be verified', addSrcFile);
-
- opts.strictMode = true; // Default mode of the linter.
+ program.name('tslinter').description('Linter for TypeScript sources').
+ version('0.0.1');
+ program.
+ option('-E, --TSC_Errors', 'show error messages from Tsc').
+ option('--relax', 'relax mode On').
+ option('--test-mode', 'run linter as if running TS test files').
+ option('--deveco-plugin-mode', 'run as IDE plugin').
+ option('-p, --project ', 'path to TS project config file').
+ option('--project-folder ', 'path to folder containig TS files to verify', addProjectFolder, []).
+ option('--autofix [autofix.json]', 'fix errors specified by JSON file (all if file is omitted)', (val: string) => {
+ return val.endsWith(JSON_EXT) ? val : true;
+ }).
+ addOption(new Option('--warnings-as-errors', 'treat warnings as errors').hideHelp(true));
+ program.argument('[srcFile...]', 'files to be verified', addSrcFile);
+
inputFiles = [];
- let cmdArgs: string[] = ['dummy', 'dummy']; // method parse() eats two first args, so make them dummy
+ // method parse() eats two first args, so make them dummy
+ let cmdArgs: string[] = ['dummy', 'dummy'];
cmdArgs.push(...commandLineArgs);
program.parse(cmdArgs);
if (responseFile !== '') {
try {
- commandLineArgs = fs.readFileSync(responseFile.slice(1)).toString().split('\n').filter((e) => e.trimEnd());
+ commandLineArgs = fs.
+ readFileSync(responseFile.slice(1)).
+ toString().
+ split('\n').
+ filter((e) => {
+ return e.trimEnd();
+ });
cmdArgs = ['dummy', 'dummy'];
cmdArgs.push(...commandLineArgs);
- program.parse( cmdArgs);
- } catch (error: any) {
- logger.error('Failed to read response file: ' + (error.message ?? error));
- process.exit(-1)
+ program.parse(cmdArgs);
+ } catch (error) {
+ loggerInstance.error('Failed to read response file: ' + error);
+ process.exit(-1);
}
}
- opts.inputFiles = inputFiles;
- const options = program.opts();
- if (options.relax) opts.strictMode = false;
- if (options.TSC_Errors) opts.logTscErrors = true;
- if (options.devecoPluginMode) opts.ideMode = true;
- if (options.testMode) opts.testMode = true;
- if (options.projectFolder) doProjectFolderArg(options.projectFolder, opts);
- if (options.project) doProjectArg(options.project, opts);
- if (options.autofix) doAutofixArg(options.autofix, opts);
- if (options.warningsAsErrors) opts.warningsAsErrors = true;
- return opts;
+ return formCommandLineOptions(program);
}
-function doProjectFolderArg(prjFolders: string[], opts: CommandLineOptions) {
- for( let i = 0; i < prjFolders.length; i++ ) {
- var prjFolderPath = prjFolders[ i ];
+function doProjectFolderArg(prjFolders: string[], opts: CommandLineOptions): void {
+ for (let i = 0; i < prjFolders.length; i++) {
+ const prjFolderPath = prjFolders[i];
try {
opts.inputFiles.push(...getFiles(prjFolderPath));
- } catch (error: any) {
- logger.error('Failed to read folder: ' + (error.message ?? error));
+ } catch (error: unknown) {
+ loggerInstance.error('Failed to read folder: ' + error);
process.exit(-1);
}
}
}
-function doProjectArg(cfgPath: string, opts: CommandLineOptions) {
+function doProjectArg(cfgPath: string, opts: CommandLineOptions): void {
// Process project file (tsconfig.json) and retrieve config arguments.
const configFile = cfgPath;
@@ -133,30 +161,36 @@ function doProjectArg(cfgPath: string, opts: CommandLineOptions) {
try {
const oldUnrecoverableDiagnostic = host.onUnRecoverableConfigFileDiagnostic;
- host.onUnRecoverableConfigFileDiagnostic = (diagnostic: ts.Diagnostic) => { diagnostics.push(diagnostic); };
+ host.onUnRecoverableConfigFileDiagnostic = (diagnostic: ts.Diagnostic): void => {
+ diagnostics.push(diagnostic);
+ };
opts.parsedConfigFile = ts.getParsedCommandLineOfConfigFile(configFile, {}, host);
host.onUnRecoverableConfigFileDiagnostic = oldUnrecoverableDiagnostic;
- if (opts.parsedConfigFile)
+ if (opts.parsedConfigFile) {
diagnostics.push(...ts.getConfigFileParsingDiagnostics(opts.parsedConfigFile));
+ }
if (diagnostics.length > 0) {
// Log all diagnostic messages and exit program.
- logger.error('Failed to read config file.');
- logTscDiagnostic(diagnostics, logger.info);
+ loggerInstance.error('Failed to read config file.');
+ logTscDiagnostic(diagnostics, loggerInstance.info);
process.exit(-1);
}
- } catch (error: any) {
- logger.error('Failed to read config file: ' + (error.message ?? error));
+ } catch (error: unknown) {
+ loggerInstance.error('Failed to read config file: ' + error);
process.exit(-1);
}
}
-function doAutofixArg(autofixOptVal: string|boolean, opts: CommandLineOptions) {
+function doAutofixArg(autofixOptVal: string | boolean, opts: CommandLineOptions): void {
if (typeof autofixOptVal === 'string') {
- let autofixInfoStr = fs.readFileSync(autofixOptVal).toString();
- let autofixInfos = JSON.parse(autofixInfoStr);
- opts.autofixInfo = autofixInfos.autofixInfo.map((x: string) => decodeAutofixInfo(x));
+ const autofixInfoStr = fs.readFileSync(autofixOptVal).toString();
+ const autofixInfos = JSON.parse(autofixInfoStr);
+ opts.autofixInfo = autofixInfos.autofixInfo.map((x: string) => {
+ return decodeAutofixInfo(x);
+ });
+ } else {
+ opts.autofixInfo = [AUTOFIX_ALL];
}
- else opts.autofixInfo = [AUTOFIX_ALL];
}
diff --git a/ets2panda/linter-4.2/src/CompilerWrapper.ts b/ets2panda/linter-4.2/src/CompilerWrapper.ts
index 8ecfa8bbf92c8f132693d2c4faa1cba39de1c102..5788123918fdd2f9dcfae7a35c0b28f000519aeb 100644
--- a/ets2panda/linter-4.2/src/CompilerWrapper.ts
+++ b/ets2panda/linter-4.2/src/CompilerWrapper.ts
@@ -16,8 +16,8 @@
import * as ts from 'typescript';
import { logTscDiagnostic } from './Utils';
import { consoleLog } from './TypeScriptLinter';
-import { CommandLineOptions } from './CommandLineOptions';
-import { LintOptions } from './LintOptions';
+import type { CommandLineOptions } from './CommandLineOptions';
+import type { LintOptions } from './LintOptions';
export function compile(options: LintOptions, extraOptions?: any): ts.Program {
const createProgramOptions = formTscOptions(options.cmdOptions, extraOptions);
@@ -41,28 +41,28 @@ export function compile(options: LintOptions, extraOptions?: any): ts.Program {
function formTscOptions(cmdOptions: CommandLineOptions, extraOptions?: any): ts.CreateProgramOptions {
if (cmdOptions.parsedConfigFile) {
- let options: ts.CreateProgramOptions = {
+ const options: ts.CreateProgramOptions = {
rootNames: cmdOptions.parsedConfigFile.fileNames,
options: cmdOptions.parsedConfigFile.options,
projectReferences: cmdOptions.parsedConfigFile.projectReferences,
- configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(cmdOptions.parsedConfigFile),
+ configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(cmdOptions.parsedConfigFile)
};
if (extraOptions) {
options.options = Object.assign(options.options, extraOptions);
}
-
+
return options;
}
- let options: ts.CreateProgramOptions = {
+ const options: ts.CreateProgramOptions = {
rootNames: cmdOptions.inputFiles,
options: {
target: ts.ScriptTarget.Latest,
module: ts.ModuleKind.CommonJS,
allowJs: true,
- checkJs: true,
- },
+ checkJs: true
+ }
};
if (extraOptions) {
diff --git a/ets2panda/linter-4.2/src/CookBookMsg.ts b/ets2panda/linter-4.2/src/CookBookMsg.ts
index c097345da5b47ffe0f98b7215329933df4889a14..5b77b6e5206566f366c83e6c3bd728a3b4ee211a 100644
--- a/ets2panda/linter-4.2/src/CookBookMsg.ts
+++ b/ets2panda/linter-4.2/src/CookBookMsg.ts
@@ -1,26 +1,27 @@
-/*
- * 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.
+/*
+ * 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.
*/
export const cookBookMsg: string[] = [];
export const cookBookTag: string[] = [];
-for (let i = 0; i <= 150; i++) {
- cookBookMsg[i] = '';
+for (let i = 0; i <= 151; i++) {
+ cookBookMsg[i] = '';
}
-cookBookTag[1] = 'Objects with property names that are not identifiers are not supported (arkts-identifiers-as-prop-names)';
+cookBookTag[1] =
+ 'Objects with property names that are not identifiers are not supported (arkts-identifiers-as-prop-names)';
cookBookTag[2] = '"Symbol()" API is not supported (arkts-no-symbol)';
cookBookTag[3] = 'Private \'#\' identifiers are not supported (arkts-no-private-identifiers)';
cookBookTag[4] = 'Use unique names for types and namespaces. (arkts-unique-names)';
@@ -56,8 +57,9 @@ cookBookTag[33] = '';
cookBookTag[34] = 'Type inference in case of generic function calls is limited (arkts-no-inferred-generic-params)';
cookBookTag[35] = '';
cookBookTag[36] = '';
-cookBookTag[37] = 'RegExp literals are not supported (arkts-no-regexp-literals)';
-cookBookTag[38] = 'Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals)';
+cookBookTag[37] = '';
+cookBookTag[38] =
+ 'Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals)';
cookBookTag[39] = '';
cookBookTag[40] = 'Object literals cannot be used as type declarations (arkts-no-obj-literals-as-types)';
cookBookTag[41] = '';
@@ -118,7 +120,8 @@ cookBookTag[95] = '';
cookBookTag[96] = 'Type guarding is supported with "instanceof" and "as" (arkts-no-is)';
cookBookTag[97] = '';
cookBookTag[98] = '';
-cookBookTag[99] = 'It is possible to spread only arrays or classes derived from arrays into the rest parameter or array literals (arkts-no-spread)';
+cookBookTag[99] =
+ 'It is possible to spread only arrays or classes derived from arrays into the rest parameter or array literals (arkts-no-spread)';
cookBookTag[100] = '';
cookBookTag[101] = '';
cookBookTag[102] = 'Interface can not extend interfaces with the same method (arkts-no-extend-same-prop)';
@@ -130,12 +133,14 @@ cookBookTag[107] = '';
cookBookTag[108] = '';
cookBookTag[109] = '';
cookBookTag[110] = '';
-cookBookTag[111] = 'Enumeration members can be initialized only with compile time expressions of the same type (arkts-no-enum-mixed-types)';
+cookBookTag[111] =
+ 'Enumeration members can be initialized only with compile time expressions of the same type (arkts-no-enum-mixed-types)';
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] = '';
-cookBookTag[116] = 'Non-declaration statements in namespaces are not supported (arkts-no-ns-statements)';
+cookBookTag[116] =
+ 'Non-declaration statements in namespaces are not supported (single semicolons are considered as empty non-declaration statements) (arkts-no-ns-statements)';
cookBookTag[117] = '';
cookBookTag[118] = 'Special import type declarations are not supported (arkts-no-special-imports)';
cookBookTag[119] = 'Importing a module for side-effects only is not supported (arkts-no-side-effects-imports)';
@@ -159,7 +164,8 @@ cookBookTag[136] = 'Prototype assignment is not supported (arkts-no-prototype-as
cookBookTag[137] = '"globalThis" is not supported (arkts-no-globalthis)';
cookBookTag[138] = 'Some of utility types are not supported (arkts-no-utility-types)';
cookBookTag[139] = 'Declaring properties on functions is not supported (arkts-no-func-props)';
-cookBookTag[140] = '"Function.apply", "Function.bind", "Function.call" are not supported (arkts-no-func-apply-bind-call)';
+cookBookTag[140] =
+ '"Function.apply", "Function.bind", "Function.call" are not supported (arkts-no-func-apply-bind-call)';
cookBookTag[141] = '';
cookBookTag[142] = '"as const" assertions are not supported (arkts-no-as-const)';
cookBookTag[143] = 'Import assertions are not supported (arkts-no-import-assertions)';
@@ -170,4 +176,4 @@ cookBookTag[147] = 'No dependencies on TypeScript code are currently allowed (ar
cookBookTag[148] = 'No decorators except ArkUI decorators are currently allowed (arkts-no-decorators-except-arkui)';
cookBookTag[149] = 'Classes cannot be used as objects (arkts-no-classes-as-obj)';
cookBookTag[150] = '"import" statements after other statements are not allowed (arkts-no-misplaced-imports)';
-cookBookTag[151] = 'Usage of "ESObject" type is restricted (arkts-limited-esobject)';
+cookBookTag[151] = 'Usage of "ESObject" type is restricted (arkts-limited-esobj)';
diff --git a/ets2panda/linter-4.2/src/DiagnosticChecker.ts b/ets2panda/linter-4.2/src/DiagnosticChecker.ts
index dd327de111e711b2e96ba421aa2e4b99ef3e2f88..b64436f904739806768401678b388b84b09eea36 100644
--- a/ets2panda/linter-4.2/src/DiagnosticChecker.ts
+++ b/ets2panda/linter-4.2/src/DiagnosticChecker.ts
@@ -16,5 +16,5 @@
import type * as ts from 'typescript';
export interface DiagnosticChecker {
- checkDiagnosticMessage(msgText: string | ts.DiagnosticMessageChain): boolean;
-}
\ No newline at end of file
+ checkDiagnosticMessage: (msgText: string | ts.DiagnosticMessageChain) => boolean;
+}
diff --git a/ets2panda/linter/src/utils/functions/GetScriptKind.ts b/ets2panda/linter-4.2/src/ForEachNodeInSubtree.ts
similarity index 53%
rename from ets2panda/linter/src/utils/functions/GetScriptKind.ts
rename to ets2panda/linter-4.2/src/ForEachNodeInSubtree.ts
index e1bedc1b38141603517ddf3b4dee0200b73458f0..08fd8e7148347756a44d6e2b653555b14444c54b 100644
--- a/ets2panda/linter/src/utils/functions/GetScriptKind.ts
+++ b/ets2panda/linter-4.2/src/ForEachNodeInSubtree.ts
@@ -14,23 +14,18 @@
*/
import * as ts from 'typescript';
-import * as path from 'node:path';
-export function getScriptKind(srcFile: ts.SourceFile): ts.ScriptKind {
- const fileName = srcFile.fileName
- const ext = path.extname(fileName).toLowerCase()
- switch (ext) {
- case ts.Extension.Ts:
- return ts.ScriptKind.TS;
- case ts.Extension.Tsx:
- return ts.ScriptKind.TSX;
- case ts.Extension.Js:
- return ts.ScriptKind.JS;
- case ts.Extension.Jsx:
- return ts.ScriptKind.JSX;
- case ts.Extension.Json:
- return ts.ScriptKind.JSON;
- default:
- return ts.ScriptKind.Unknown;
+export function forEachNodeInSubtree(
+ node: ts.Node,
+ cb: (n: ts.Node) => void,
+ stopCond?: (n: ts.Node) => boolean
+): void {
+ cb(node);
+ if (stopCond && stopCond(node)) {
+ return;
}
-}
\ No newline at end of file
+
+ ts.forEachChild(node, (child) => {
+ forEachNodeInSubtree(child, cb, stopCond);
+ });
+}
diff --git a/ets2panda/linter-4.2/src/IsFileFromModuleCallback.ts b/ets2panda/linter-4.2/src/IsFileFromModuleCallback.ts
new file mode 100644
index 0000000000000000000000000000000000000000..46ed92b861d4da1be3221f33ded3a68d9166a41d
--- /dev/null
+++ b/ets2panda/linter-4.2/src/IsFileFromModuleCallback.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 type IsFileFromModuleCallback = (fileFsPath: string) => boolean;
diff --git a/ets2panda/linter-4.2/src/LibraryTypeCallDiagnosticChecker.ts b/ets2panda/linter-4.2/src/LibraryTypeCallDiagnosticChecker.ts
index e80b4025a56a37011308d42eb0727c3687e73210..5f59db087dd7437ffb7d4f76b1ddef45963a6253 100644
--- a/ets2panda/linter-4.2/src/LibraryTypeCallDiagnosticChecker.ts
+++ b/ets2panda/linter-4.2/src/LibraryTypeCallDiagnosticChecker.ts
@@ -13,19 +13,25 @@
* limitations under the License.
*/
-import * as ts from 'typescript';
-import { DiagnosticChecker } from './DiagnosticChecker';
+import type * as ts from 'typescript';
+import type { DiagnosticChecker } from './DiagnosticChecker';
-// Current approach relates on error code and error message matching and it is quite fragile,
-// so this place should be checked thoroughly in the case of typescript upgrade
+/*
+ * Current approach relates on error code and error message matching and it is quite fragile,
+ * so this place should be checked thoroughly in the case of typescript upgrade
+ */
export const TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE = 2322;
-export const TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bunknown\b(.*)' is not assignable to type '.*'\.$/;
-export const TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type 'null' is not assignable to type '.*'\.$/;
-export const TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type 'undefined' is not assignable to type '.*'\.$/;
+export const TYPE_UNKNOWN_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE =
+ /^Type '(.*)\bunknown\b(.*)' is not assignable to type '.*'\.$/;
+export const TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE = /^Type '(.*)\bnull\b(.*)' is not assignable to type '.*'\.$/;
+export const TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE =
+ /^Type '(.*)\bundefined\b(.*)' is not assignable to type '.*'\.$/;
export const ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE = 2345;
-export const ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE = /^Argument of type 'null' is not assignable to parameter of type '.*'\.$/;
-export const ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE = /^Argument of type 'undefined' is not assignable to parameter of type '.*'\.$/;
+export const ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE =
+ /^Argument of type '(.*)\bnull\b(.*)' is not assignable to parameter of type '.*'\.$/;
+export const ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE =
+ /^Argument of type '(.*)\bundefined\b(.*)' is not assignable to parameter of type '.*'\.$/;
export class LibraryTypeCallDiagnosticChecker implements DiagnosticChecker {
inLibCall: boolean = false;
@@ -43,7 +49,8 @@ export class LibraryTypeCallDiagnosticChecker implements DiagnosticChecker {
checkMessageText(msg: string): boolean {
if (this.inLibCall) {
- const match = msg.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) ||
+ const match =
+ msg.match(ARGUMENT_OF_TYPE_NULL_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) ||
msg.match(ARGUMENT_OF_TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_RE) ||
msg.match(TYPE_UNDEFINED_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE) ||
msg.match(TYPE_NULL_IS_NOT_ASSIGNABLE_TO_TYPE_1_RE);
@@ -127,4 +134,4 @@ export class LibraryTypeCallDiagnosticChecker implements DiagnosticChecker {
}
return true;
}
-}
\ No newline at end of file
+}
diff --git a/ets2panda/linter/src/utils/consts/LimitedStdApi.ts b/ets2panda/linter-4.2/src/LimitedStdAPI.ts
similarity index 51%
rename from ets2panda/linter/src/utils/consts/LimitedStdApi.ts
rename to ets2panda/linter-4.2/src/LimitedStdAPI.ts
index 1599fbfe9210a7b902bfffac2596ea6a87fe3a28..8e28dfa76e1bc4c88dd4b7f4d287a0a5e4e0452b 100644
--- a/ets2panda/linter/src/utils/consts/LimitedStdApi.ts
+++ b/ets2panda/linter-4.2/src/LimitedStdAPI.ts
@@ -13,23 +13,35 @@
* limitations under the License.
*/
-import { FaultID } from "./Problems";
+import { FaultID } from './Problems';
-/**
- * ArrayBuffer
- */
-const LIMITED_STD_ARRAYBUFFER_API = [
+const LIMITED_STD_SYMBOL_API = [
// properties
+ 'asyncIterator',
+ 'description',
+ 'hasInstance',
+ 'isConcatSpreadable',
+ 'match',
+ 'matchAll',
+ 'replace',
+ 'search',
+ 'species',
+ 'split',
+ 'toPrimitive',
+ 'toStringTag',
+ 'unscopables',
+
// methods
- 'isView'
+ 'for',
+ 'keyFor',
+ 'toString',
+ 'valueOf'
];
-/**
- * Object
- */
const LIMITED_STD_OBJECT_API = [
// properties
'__proto__',
+
// methods
'__defineGetter__',
'__defineSetter__',
@@ -54,14 +66,12 @@ const LIMITED_STD_OBJECT_API = [
'preventExtensions',
'propertyIsEnumerable',
'seal',
- 'setPrototypeOf',
+ 'setPrototypeOf'
];
-/**
- * Proxy
- */
const LIMITED_STD_PROXYHANDLER_API = [
// properties
+
// methods
'apply',
'construct',
@@ -78,11 +88,9 @@ const LIMITED_STD_PROXYHANDLER_API = [
'setPrototypeOf'
];
-/**
- * Reflect
- */
const LIMITED_STD_REFLECT_API = [
// properties
+
// methods
'apply',
'construct',
@@ -92,64 +100,51 @@ const LIMITED_STD_REFLECT_API = [
'getPrototypeOf',
'isExtensible',
'preventExtensions',
- 'setPrototypeOf',
+ 'setPrototypeOf'
];
-/**
- * Symbol
- */
-const LIMITED_STD_SYMBOL_API = [
- 'Symbol',
- // properties
- 'asyncIterator',
- 'description',
- 'hasInstance',
- 'isConcatSpreadable',
- 'match',
- 'matchAll',
- 'replace',
- 'search',
- 'species',
- 'split',
- 'toPrimitive',
- 'toStringTag',
- 'unscopables',
- // methods
- 'for',
- 'keyFor',
- 'toString',
- 'valueOf',
+const LIMITED_STD_FUNCTION_API_APPLY_CALL = [
+ // properties
+
+ // methods
+ 'apply',
+ 'call'
];
-/**
- * Function
- */
-const LIMITED_STD_FUNCTION_API = [
+const LIMITED_STD_FUNCTION_API_BIND = [
// properties
+
// methods
- 'apply',
- 'bind',
- 'call',
+ 'bind'
];
-/**
- * Global
- */
-export const LIMITED_STD_GLOBAL_API = [
+const LIMITED_STD_GLOBAL_API = [
// properties
+
// methods
- 'eval',
+ 'eval'
];
-export const LIMITED_STD_API = new Map, fault: FaultID}> ([
- ['Object', {arr: LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}],
- ['ObjectConstructor', {arr: LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}],
- ['Reflect', {arr: LIMITED_STD_REFLECT_API, fault: FaultID.LimitedStdLibApi}],
- ['ProxyHandler', {arr: LIMITED_STD_PROXYHANDLER_API, fault: FaultID.LimitedStdLibApi}],
- ['ArrayBuffer', {arr: LIMITED_STD_ARRAYBUFFER_API, fault: FaultID.LimitedStdLibApi}],
- ['ArrayBufferConstructor', {arr: LIMITED_STD_ARRAYBUFFER_API, fault: FaultID.LimitedStdLibApi}],
- ['Symbol', {arr: LIMITED_STD_SYMBOL_API, fault: FaultID.SymbolType}],
- ['SymbolConstructor', {arr: LIMITED_STD_SYMBOL_API, fault: FaultID.SymbolType}],
- ['Function', {arr: LIMITED_STD_FUNCTION_API, fault: FaultID.FunctionApplyBindCall}],
- ['CallableFunction', {arr: LIMITED_STD_FUNCTION_API, fault: FaultID.FunctionApplyBindCall}],
-])
+const STD_SYMBOL_ENTRY = [{ api: LIMITED_STD_SYMBOL_API, faultId: FaultID.SymbolType }];
+const STD_OBJECT_ENTRY = [{ api: LIMITED_STD_OBJECT_API, faultId: FaultID.LimitedStdLibApi }];
+const STD_PROXYHANDLER_ENTRY = [{ api: LIMITED_STD_PROXYHANDLER_API, faultId: FaultID.LimitedStdLibApi }];
+const STD_REFLECT_ENTRY = [{ api: LIMITED_STD_REFLECT_API, faultId: FaultID.LimitedStdLibApi }];
+const STD_FUNCTION_ENTRY = [
+ { api: LIMITED_STD_FUNCTION_API_APPLY_CALL, faultId: FaultID.FunctionApplyCall },
+ { api: LIMITED_STD_FUNCTION_API_BIND, faultId: FaultID.FunctionBind }
+];
+const STD_GLOBAL_ENTRY = [{ api: LIMITED_STD_GLOBAL_API, faultId: FaultID.LimitedStdLibApi }];
+
+export type LimitedStdLibApiEntry = { api: string[]; faultId: FaultID };
+
+export const LIMITED_STD_API = new Map([
+ [undefined, STD_GLOBAL_ENTRY],
+ ['Object', STD_OBJECT_ENTRY],
+ ['ObjectConstructor', STD_OBJECT_ENTRY],
+ ['Reflect', STD_REFLECT_ENTRY],
+ ['ProxyHandler', STD_PROXYHANDLER_ENTRY],
+ ['Symbol', STD_SYMBOL_ENTRY],
+ ['SymbolConstructor', STD_SYMBOL_ENTRY],
+ ['Function', STD_FUNCTION_ENTRY],
+ ['CallableFunction', STD_FUNCTION_ENTRY]
+]);
diff --git a/ets2panda/linter-4.2/src/LintOptions.ts b/ets2panda/linter-4.2/src/LintOptions.ts
index a2d1184f59fbf5189cc5e18a23cafab7b9c7fd37..d25e3a3bd824b26bed798a0a56e500e4a8c83903 100644
--- a/ets2panda/linter-4.2/src/LintOptions.ts
+++ b/ets2panda/linter-4.2/src/LintOptions.ts
@@ -13,12 +13,14 @@
* limitations under the License.
*/
-import * as ts from 'typescript';
-import { CommandLineOptions } from './CommandLineOptions';
+import type * as ts from 'typescript';
+import type { CommandLineOptions } from './CommandLineOptions';
+import type { IsFileFromModuleCallback } from './IsFileFromModuleCallback';
// common options interface, additional fields may be used by plugins
export interface LintOptions {
cmdOptions: CommandLineOptions;
tsProgram?: ts.Program;
+ isFileFromModuleCb?: IsFileFromModuleCallback;
[key: string]: any;
}
diff --git a/ets2panda/linter-4.2/src/LintRunResult.ts b/ets2panda/linter-4.2/src/LintRunResult.ts
index 565b7bf6dfb2d7a980f25519eecb08ede6d686f7..751cc8d6e80148bd6d248441440480058f680102 100644
--- a/ets2panda/linter-4.2/src/LintRunResult.ts
+++ b/ets2panda/linter-4.2/src/LintRunResult.ts
@@ -13,9 +13,9 @@
* limitations under the License.
*/
-import { ProblemInfo } from './ProblemInfo';
+import type { ProblemInfo } from './ProblemInfo';
export interface LintRunResult {
errorNodes: number;
problemsInfos: Map;
-}
\ No newline at end of file
+}
diff --git a/ets2panda/linter-4.2/src/LinterRunner.ts b/ets2panda/linter-4.2/src/LinterRunner.ts
index 466e245285f03a5ef0f951940c25006a7820c2ed..fc0be1fc4f6ee498eb8fdcdd9967b97d675fdfd2 100644
--- a/ets2panda/linter-4.2/src/LinterRunner.ts
+++ b/ets2panda/linter-4.2/src/LinterRunner.ts
@@ -13,58 +13,104 @@
* limitations under the License.
*/
-import * as ts from 'typescript';
-import { ProblemInfo } from './ProblemInfo';
+import type * as ts from 'typescript';
+import type { 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 type { 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 } from './CommandLineOptions';
-import { LintOptions } from './LintOptions';
+import type { CommandLineOptions } from './CommandLineOptions';
+import type { LintOptions } from './LintOptions';
import { AutofixInfoSet } from './Autofixer';
import { TSCCompiledProgram, getStrictOptions, transformDiagnostic } from './ts-diagnostics/TSCCompiledProgram';
import { mergeArrayMaps, pathContainsDirectory, TsUtils } from './Utils';
+import { ProblemSeverity } from './ProblemSeverity';
-const logger = Logger.getLogger();
+const loggerInstance = logger.getLogger();
// Use static init method for Linter configuration, as TypeScript 4.2 doesn't support static blocks.
LinterConfig.initStatic();
-export function lint(options: LintOptions): LintRunResult {
- const cmdOptions = options.cmdOptions;
-
- const tscDiagnosticsLinter = createLinter(options);
- const tsProgram = tscDiagnosticsLinter.getOriginalProgram();
+function toFixedPercent(part: number): string {
+ const percentiles = 100;
+ const precision = 2;
+ return (part * percentiles).toFixed(precision);
+}
- // Prepare list of input files for linter and retrieve AST for those files.
- let inputFiles: string[] = cmdOptions.inputFiles;
+function prepareInputFilesList(cmdOptions: CommandLineOptions): string[] {
+ let inputFiles = cmdOptions.inputFiles;
if (cmdOptions.parsedConfigFile) {
inputFiles = cmdOptions.parsedConfigFile.fileNames;
if (cmdOptions.inputFiles.length > 0) {
- // Apply linter only to the project source files that are specified
- // as a command-line arguments. Other source files will be discarded.
- const cmdInputsResolvedPaths = cmdOptions.inputFiles.map((x) => path.resolve(x));
- const configInputsResolvedPaths = inputFiles.map((x) => path.resolve(x));
- inputFiles = configInputsResolvedPaths.filter((x) => cmdInputsResolvedPaths.some((y) => x === y));
+
+ /*
+ * Apply linter only to the project source files that are specified
+ * as a command-line arguments. Other source files will be discarded.
+ */
+ const cmdInputsResolvedPaths = cmdOptions.inputFiles.map((x) => {
+ return path.resolve(x);
+ });
+ const configInputsResolvedPaths = inputFiles.map((x) => {
+ return path.resolve(x);
+ });
+ inputFiles = configInputsResolvedPaths.filter((x) => {
+ return cmdInputsResolvedPaths.some((y) => {
+ return x === y;
+ });
+ });
}
}
- // #13436: ignore-list for ArkTS projects.
- inputFiles = inputFiles.filter(input =>
- !TsUtils.ARKTS_IGNORE_FILES.some(ignore => path.basename(input) === ignore) &&
- !TsUtils.ARKTS_IGNORE_DIRS.some(ignore => pathContainsDirectory(path.resolve(input), ignore)));
+ return inputFiles;
+}
+
+function countProblems(linter: TypeScriptLinter): [number, number] {
+ let errorNodesTotal = 0;
+ let warningNodes = 0;
+ for (let i = 0; i < FaultID.LAST_ID; i++) {
+ // if Strict mode - count all cases
+ if (!linter.strictMode && faultsAttrs[i].migratable) {
+ // In relax mode skip migratable
+ continue;
+ }
+ switch (faultsAttrs[i].severity) {
+ case ProblemSeverity.ERROR:
+ errorNodesTotal += linter.nodeCounters[i];
+ break;
+ case ProblemSeverity.WARNING:
+ warningNodes += linter.nodeCounters[i];
+ break;
+ }
+ }
+
+ return [errorNodesTotal, warningNodes];
+}
+
+export function lint(options: LintOptions): LintRunResult {
+ const cmdOptions = options.cmdOptions;
+
+ const tscDiagnosticsLinter = createLinter(options);
+ const tsProgram = tscDiagnosticsLinter.getOriginalProgram();
+
+ // Prepare list of input files for linter and retrieve AST for those files.
+ let inputFiles = prepareInputFilesList(cmdOptions);
+ inputFiles = inputFiles.filter((input) => {
+ return shouldProcessFile(options, input);
+ });
const srcFiles: ts.SourceFile[] = [];
for (const inputFile of inputFiles) {
const srcFile = tsProgram.getSourceFile(inputFile);
- if (srcFile) srcFiles.push(srcFile);
+ if (srcFile) {
+ srcFiles.push(srcFile);
+ }
}
const tscStrictDiagnostics = getTscDiagnostics(tscDiagnosticsLinter, srcFiles);
@@ -81,34 +127,55 @@ export function lint(options: LintOptions): LintRunResult {
consoleLog('\n\n\nFiles scanned: ', srcFiles.length);
consoleLog('\nFiles with problems: ', errorNodes);
- let errorNodesTotal = 0, warningNodes = 0;
- for (let i = 0; i < FaultID.LAST_ID; i++) {
- // if Strict mode - count all cases
- if (!linter.strictMode && faultsAttrs[i].migratable) // In relax mode skip migratable
- continue;
+ const [errorNodesTotal, warningNodes] = countProblems(linter);
- if (faultsAttrs[i].warning) warningNodes += linter.nodeCounters[i];
- else errorNodesTotal += linter.nodeCounters[i];
- }
logTotalProblemsInfo(errorNodesTotal, warningNodes, linter);
logProblemsPercentageByFeatures(linter);
+
return {
errorNodes: errorNodesTotal,
- problemsInfos: mergeArrayMaps(problemsInfos, transformTscDiagnostics(tscStrictDiagnostics)),
+ problemsInfos: mergeArrayMaps(problemsInfos, transformTscDiagnostics(tscStrictDiagnostics))
};
}
+/*
+ * We want linter to accept program with no strict options set at all
+ * due to them affecting type deduction.
+ */
+function clearStrictOptions(options: LintOptions): LintOptions {
+ if (!options.parserOptions) {
+ return options;
+ }
+ const newOptions = { ...options };
+ newOptions.parserOptions.strict = false;
+ newOptions.parserOptions.alwaysStrict = false;
+ newOptions.parserOptions.noImplicitAny = false;
+ newOptions.parserOptions.noImplicitThis = false;
+ newOptions.parserOptions.strictBindCallApply = false;
+ newOptions.parserOptions.strictFunctionTypes = false;
+ newOptions.parserOptions.strictNullChecks = false;
+ newOptions.parserOptions.strictPropertyInitialization = false;
+ newOptions.parserOptions.useUnknownInCatchVariables = false;
+ return newOptions;
+}
+
export function createLinter(options: LintOptions): TSCCompiledProgram {
if (options.tscDiagnosticsLinter) {
return options.tscDiagnosticsLinter;
}
- const tsProgram = options.tsProgram ?? compile(options, getStrictOptions());
- return new TSCCompiledProgram(tsProgram, options);
+
+ /*
+ * if we are provided with the pre-compiled program, don't tamper with options
+ * otherwise, clear all strict related options to avoid differences in type deduction
+ */
+ const newOptions = options.tsProgram ? options : clearStrictOptions(options);
+ const tsProgram = newOptions.tsProgram ?? compile(newOptions, getStrictOptions());
+ return new TSCCompiledProgram(tsProgram, newOptions);
}
function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter): LintRunResult {
let problemFiles = 0;
- let problemsInfos: Map = new Map();
+ const problemsInfos: Map = new Map();
for (const srcFile of srcFiles) {
const prevVisitedNodes = linter.totalVisitedNodes;
@@ -118,12 +185,13 @@ function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter): LintRun
linter.warningLineNumbersString = '';
const nodeCounters: number[] = [];
- for (let i = 0; i < FaultID.LAST_ID; i++)
+ for (let i = 0; i < FaultID.LAST_ID; i++) {
nodeCounters[i] = linter.nodeCounters[i];
+ }
linter.lint(srcFile);
// save results and clear problems array
- problemsInfos.set( path.normalize(srcFile.fileName), [...linter.problemsInfos]);
+ problemsInfos.set(path.normalize(srcFile.fileName), [...linter.problemsInfos]);
linter.problemsInfos.length = 0;
// print results for current file
@@ -132,13 +200,19 @@ function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter): LintRun
const fileWarningLines = linter.totalWarningLines - prevWarningLines;
problemFiles = countProblemFiles(
- nodeCounters, problemFiles, srcFile, fileVisitedNodes, fileErrorLines, fileWarningLines, linter
+ nodeCounters,
+ problemFiles,
+ srcFile,
+ fileVisitedNodes,
+ fileErrorLines,
+ fileWarningLines,
+ linter
);
}
return {
errorNodes: problemFiles,
- problemsInfos: problemsInfos,
+ problemsInfos: problemsInfos
};
}
@@ -151,10 +225,10 @@ function lintFiles(srcFiles: ts.SourceFile[], linter: TypeScriptLinter): LintRun
*/
function getTscDiagnostics(
tscDiagnosticsLinter: TSCCompiledProgram,
- sourceFiles: ts.SourceFile[],
+ sourceFiles: ts.SourceFile[]
): Map {
const strictDiagnostics = new Map();
- sourceFiles.forEach(file => {
+ sourceFiles.forEach((file) => {
const diagnostics = tscDiagnosticsLinter.getStrictDiagnostics(file.fileName);
if (diagnostics.length !== 0) {
strictDiagnostics.set(path.normalize(file.fileName), diagnostics);
@@ -163,68 +237,85 @@ function getTscDiagnostics(
return strictDiagnostics;
}
-function transformTscDiagnostics(
- strictDiagnostics: Map
-): Map {
+function transformTscDiagnostics(strictDiagnostics: Map): Map {
const problemsInfos = new Map();
- strictDiagnostics.forEach((diagnostics, file, map) => {
- problemsInfos.set(file, diagnostics.map(x => transformDiagnostic(x)));
+ strictDiagnostics.forEach((diagnostics, file) => {
+ problemsInfos.set(
+ file,
+ diagnostics.map((x) => {
+ return transformDiagnostic(x);
+ })
+ );
});
return problemsInfos;
}
function countProblemFiles(
- nodeCounters: number[], filesNumber: number, tsSrcFile: ts.SourceFile,
- fileNodes: number, fileErrorLines: number, fileWarningLines: number, linter: TypeScriptLinter,
-) {
- let errorNodes = 0, warningNodes = 0;
+ nodeCounters: number[],
+ filesNumber: number,
+ tsSrcFile: ts.SourceFile,
+ fileNodes: number,
+ fileErrorLines: number,
+ fileWarningLines: number,
+ linter: TypeScriptLinter
+): number {
+ let errorNodes = 0;
+ let warningNodes = 0;
for (let i = 0; i < FaultID.LAST_ID; i++) {
- let nodeCounterDiff = linter.nodeCounters[i] - nodeCounters[i];
- if (faultsAttrs[i].warning) warningNodes += nodeCounterDiff;
- else errorNodes += nodeCounterDiff;
+ const nodeCounterDiff = linter.nodeCounters[i] - nodeCounters[i];
+ switch (faultsAttrs[i].severity) {
+ case ProblemSeverity.ERROR:
+ errorNodes += nodeCounterDiff;
+ break;
+ case ProblemSeverity.WARNING:
+ warningNodes += nodeCounterDiff;
+ break;
+ }
}
-
if (errorNodes > 0) {
filesNumber++;
- let errorRate = ((errorNodes / fileNodes) * 100).toFixed(2);
- let warningRate = ((warningNodes / fileNodes) * 100).toFixed(2);
+ const errorRate = toFixedPercent(errorNodes / fileNodes);
+ const warningRate = toFixedPercent(warningNodes / fileNodes);
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');
+ consoleLog(`\n\tError constructs (%): ${errorRate}\t[ of ${fileNodes} constructs ], \t${fileErrorLines} lines`);
+ consoleLog(
+ `\n\tWarning constructs (%): ${warningRate}\t[ of ${fileNodes} constructs ], \t${fileWarningLines} lines`
+ );
}
-
return filesNumber;
}
-function logTotalProblemsInfo(errorNodes: number, warningNodes: number, linter: TypeScriptLinter) {
- let errorRate = ((errorNodes / linter.totalVisitedNodes) * 100).toFixed(2);
- let warningRate = ((warningNodes / linter.totalVisitedNodes) * 100).toFixed(2);
+function logTotalProblemsInfo(errorNodes: number, warningNodes: number, linter: TypeScriptLinter): void {
+ const errorRate = toFixedPercent(errorNodes / linter.totalVisitedNodes);
+ const warningRate = toFixedPercent(warningNodes / linter.totalVisitedNodes);
consoleLog('\nTotal error constructs (%): ', errorRate);
consoleLog('\nTotal warning constructs (%): ', warningRate);
consoleLog('\nTotal error lines:', linter.totalErrorLines, ' lines\n');
consoleLog('\nTotal warning lines:', linter.totalWarningLines, ' lines\n');
}
-function logProblemsPercentageByFeatures(linter: TypeScriptLinter) {
+function logProblemsPercentageByFeatures(linter: TypeScriptLinter): void {
consoleLog('\nPercent by features: ');
+ const paddingPercentage = 7;
for (let i = 0; i < FaultID.LAST_ID; i++) {
// if Strict mode - count all cases
- if (!linter.strictMode && faultsAttrs[i].migratable)
+ if (!linter.strictMode && faultsAttrs[i].migratable) {
continue;
+ }
- let nodes = linter.nodeCounters[i];
- let lines = linter.lineCounters[i];
- let pecentage = ((nodes / linter.totalVisitedNodes) * 100).toFixed(2).padEnd(7, ' ');
+ const nodes = linter.nodeCounters[i];
+ const lines = linter.lineCounters[i];
+ const pecentage = toFixedPercent(nodes / linter.totalVisitedNodes).padEnd(paddingPercentage, ' ');
consoleLog(LinterConfig.nodeDesc[i].padEnd(55, ' '), pecentage, '[', nodes, ' constructs / ', lines, ' lines]');
}
}
-export function run() {
+export function run(): void {
const commandLineArgs = process.argv.slice(2);
if (commandLineArgs.length === 0) {
- logger.info('Command line error: no arguments');
+ loggerInstance.info('Command line error: no arguments');
process.exit(-1);
}
@@ -244,11 +335,12 @@ export function run() {
}
}
-function getTempFileName() {
- return path.join(os.tmpdir(), Math.floor(Math.random() * 10000000).toString() + '_linter_tmp_file.ts');
+function getTempFileName(): string {
+ const bigNumber = 10000000;
+ return path.join(os.tmpdir(), Math.floor(Math.random() * bigNumber).toString() + '_linter_tmp_file.ts');
}
-function runIDEMode(cmdOptions: CommandLineOptions) {
+function runIDEMode(cmdOptions: CommandLineOptions): void {
TypeScriptLinter.ideMode = true;
const tmpFileName = getTempFileName();
// read data from stdin
@@ -256,10 +348,12 @@ function runIDEMode(cmdOptions: CommandLineOptions) {
const rl = readline.createInterface({
input: process.stdin,
output: writeStream,
- terminal: false,
+ terminal: false
});
- rl.on('line', (line: string) => { fs.appendFileSync(tmpFileName, line + '\n'); });
+ rl.on('line', (line: string) => {
+ fs.appendFileSync(tmpFileName, line + '\n');
+ });
rl.once('close', () => {
// end of input
writeStream.close();
@@ -270,22 +364,47 @@ function runIDEMode(cmdOptions: CommandLineOptions) {
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)}}`);
+ const jsonMessage = problems[0].map((x) => {
+ return {
+ 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
+ };
+ });
+ loggerInstance.info(`{"linter messages":${JSON.stringify(jsonMessage)}}`);
} else {
- logger.error('Unexpected error: could not lint file');
+ loggerInstance.error('Unexpected error: could not lint file');
}
fs.unlinkSync(tmpFileName);
});
}
+
+function shouldProcessFile(options: LintOptions, fileFsPath: string): boolean {
+ if (
+ TsUtils.ARKTS_IGNORE_FILES.some((ignore) => {
+ return path.basename(fileFsPath) === ignore;
+ })
+ ) {
+ return false;
+ }
+
+ if (
+ TsUtils.ARKTS_IGNORE_DIRS_NO_OH_MODULES.some((ignore) => {
+ return pathContainsDirectory(path.resolve(fileFsPath), ignore);
+ })
+ ) {
+ return false;
+ }
+
+ return (
+ !pathContainsDirectory(path.resolve(fileFsPath), TsUtils.ARKTS_IGNORE_DIRS_OH_MODULES) ||
+ options.isFileFromModuleCb !== undefined && options.isFileFromModuleCb(fileFsPath)
+ );
+}
diff --git a/ets2panda/linter-4.2/src/ProblemInfo.ts b/ets2panda/linter-4.2/src/ProblemInfo.ts
index 0339677419324bf539b2ee156f874fc44e3264b3..94f4cdeb86ba9ca4dd2f0081fbc9fe951720cbb3 100644
--- a/ets2panda/linter-4.2/src/ProblemInfo.ts
+++ b/ets2panda/linter-4.2/src/ProblemInfo.ts
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-import { Autofix } from './Autofixer';
+import type { Autofix } from './Autofixer';
export interface ProblemInfo {
line: number;
diff --git a/ets2panda/linter-4.2/src/ProblemSeverity.ts b/ets2panda/linter-4.2/src/ProblemSeverity.ts
index 3736c692ef46726f5ecced3be6290c18c371f039..e89dc4a78f69f1c70d9e3c4341cd7d8e8e71b917 100644
--- a/ets2panda/linter-4.2/src/ProblemSeverity.ts
+++ b/ets2panda/linter-4.2/src/ProblemSeverity.ts
@@ -13,4 +13,7 @@
* limitations under the License.
*/
-export enum ProblemSeverity { WARNING = 1, ERROR = 2 }
+export enum ProblemSeverity {
+ WARNING = 1,
+ ERROR = 2
+}
diff --git a/ets2panda/linter-4.2/src/Problems.ts b/ets2panda/linter-4.2/src/Problems.ts
index 98bd4701b9054be916c8f229cada1cde816e8dad..e35662a38c8b8453f9af3017897757e170eedd1e 100644
--- a/ets2panda/linter-4.2/src/Problems.ts
+++ b/ets2panda/linter-4.2/src/Problems.ts
@@ -13,121 +13,188 @@
* limitations under the License.
*/
+import { ProblemSeverity } from './ProblemSeverity';
+
export enum FaultID {
- AnyType, SymbolType, ObjectLiteralNoContextType, ArrayLiteralNoContextType,
- ComputedPropertyName, LiteralAsPropertyName, TypeQuery, RegexLiteral, IsOperator,
- DestructuringParameter, YieldExpression, InterfaceMerging, EnumMerging, InterfaceExtendsClass, IndexMember, WithStatement,
- ThrowStatement, IndexedAccessType, UnknownType, ForInStatement, InOperator,
- ImportFromPath, FunctionExpression, IntersectionType,
- ObjectTypeLiteral, CommaOperator, LimitedReturnTypeInference,
- LambdaWithTypeParameters, ClassExpression, DestructuringAssignment,
- DestructuringDeclaration, VarDeclaration, CatchWithUnsupportedType, DeleteOperator,
- DeclWithDuplicateName, UnaryArithmNotNumber, ConstructorType, ConstructorIface, ConstructorFuncs, CallSignature,
- TypeAssertion, PrivateIdentifier, LocalFunction,
- ConditionalType, MappedType, NamespaceAsObject, ClassAsObject,
- NonDeclarationInNamespace, GeneratorFunction, FunctionContainsThis, PropertyAccessByIndex, JsxElement,
- EnumMemberNonConstInit, ImplementsClass, MethodReassignment, MultipleStaticBlocks, ThisType,
- IntefaceExtendDifProps, StructuralIdentity, TypeOnlyImport, TypeOnlyExport, DefaultImport,
- ExportAssignment, ImportAssignment,
- GenericCallNoTypeArgs, ParameterProperties,
- InstanceofUnsupported, ShorthandAmbientModuleDecl, WildcardsInModuleName, UMDModuleDefinition,
- NewTarget, DefiniteAssignment, Prototype, GlobalThis,
- UtilityType, PropertyDeclOnFunction, FunctionApplyBindCall, ConstAssertion, ImportAssertion,
- SpreadOperator, LimitedStdLibApi, ErrorSuppression, StrictDiagnostic, UnsupportedDecorators, ImportAfterStatement,
- EsObjectType, LAST_ID, // this should always be last enum
+ AnyType,
+ SymbolType,
+ ObjectLiteralNoContextType,
+ ArrayLiteralNoContextType,
+ ComputedPropertyName,
+ LiteralAsPropertyName,
+ TypeQuery,
+ IsOperator,
+ DestructuringParameter,
+ YieldExpression,
+ InterfaceMerging,
+ EnumMerging,
+ InterfaceExtendsClass,
+ IndexMember,
+ WithStatement,
+ ThrowStatement,
+ IndexedAccessType,
+ UnknownType,
+ ForInStatement,
+ InOperator,
+ ImportFromPath,
+ FunctionExpression,
+ IntersectionType,
+ ObjectTypeLiteral,
+ CommaOperator,
+ LimitedReturnTypeInference,
+ LambdaWithTypeParameters,
+ ClassExpression,
+ DestructuringAssignment,
+ DestructuringDeclaration,
+ VarDeclaration,
+ CatchWithUnsupportedType,
+ DeleteOperator,
+ DeclWithDuplicateName,
+ UnaryArithmNotNumber,
+ ConstructorType,
+ ConstructorIface,
+ ConstructorFuncs,
+ CallSignature,
+ TypeAssertion,
+ PrivateIdentifier,
+ LocalFunction,
+ ConditionalType,
+ MappedType,
+ NamespaceAsObject,
+ ClassAsObject,
+ NonDeclarationInNamespace,
+ GeneratorFunction,
+ FunctionContainsThis,
+ PropertyAccessByIndex,
+ JsxElement,
+ EnumMemberNonConstInit,
+ ImplementsClass,
+ MethodReassignment,
+ MultipleStaticBlocks,
+ ThisType,
+ IntefaceExtendDifProps,
+ StructuralIdentity,
+ DefaultImport,
+ ExportAssignment,
+ ImportAssignment,
+ GenericCallNoTypeArgs,
+ ParameterProperties,
+ InstanceofUnsupported,
+ ShorthandAmbientModuleDecl,
+ WildcardsInModuleName,
+ UMDModuleDefinition,
+ NewTarget,
+ DefiniteAssignment,
+ Prototype,
+ GlobalThis,
+ UtilityType,
+ PropertyDeclOnFunction,
+ FunctionApplyCall,
+ FunctionBind,
+ ConstAssertion,
+ ImportAssertion,
+ SpreadOperator,
+ LimitedStdLibApi,
+ ErrorSuppression,
+ StrictDiagnostic,
+ UnsupportedDecorators,
+ ImportAfterStatement,
+ EsObjectType,
+ // this should always be last enum
+ LAST_ID
}
-export class FaultAttributs {
- migratable?: boolean;
- warning?: boolean;
- cookBookRef = '-1';
+export class FaultAttributes {
+ constructor(
+ public cookBookRef: number,
+ public migratable: boolean = false,
+ public severity: ProblemSeverity = ProblemSeverity.ERROR
+ ) {}
}
-export const faultsAttrs: FaultAttributs[] = [];
+export const faultsAttrs: FaultAttributes[] = [];
-faultsAttrs[FaultID.LiteralAsPropertyName] = {migratable: true, cookBookRef: '1',};
-faultsAttrs[FaultID.ComputedPropertyName] = {cookBookRef: '1',};
-faultsAttrs[FaultID.SymbolType] = {cookBookRef: '2',};
-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.UnknownType] = {cookBookRef: '8',};
-faultsAttrs[FaultID.CallSignature] = {cookBookRef: '14',};
-faultsAttrs[FaultID.ConstructorType] = {cookBookRef: '15',};
-faultsAttrs[FaultID.MultipleStaticBlocks] = {cookBookRef: '16',};
-faultsAttrs[FaultID.IndexMember] = {cookBookRef: '17',};
-faultsAttrs[FaultID.IntersectionType] = {cookBookRef: '19',};
-faultsAttrs[FaultID.ThisType] = {cookBookRef: '21',};
-faultsAttrs[FaultID.ConditionalType] = {cookBookRef: '22',};
-faultsAttrs[FaultID.ParameterProperties] = {migratable: true, cookBookRef: '25',};
-faultsAttrs[FaultID.ConstructorIface] = {cookBookRef: '27',};
-faultsAttrs[FaultID.IndexedAccessType] = {cookBookRef: '28',};
-faultsAttrs[FaultID.PropertyAccessByIndex] = {migratable: true, cookBookRef: '29',};
-faultsAttrs[FaultID.StructuralIdentity] = {cookBookRef: '30',};
-faultsAttrs[FaultID.GenericCallNoTypeArgs] = {cookBookRef: '34',};
-faultsAttrs[FaultID.RegexLiteral] = {cookBookRef: '37',};
-faultsAttrs[FaultID.ObjectLiteralNoContextType] = {cookBookRef: '38',};
-faultsAttrs[FaultID.ObjectTypeLiteral] = {cookBookRef: '40',};
-faultsAttrs[FaultID.ArrayLiteralNoContextType] = {cookBookRef: '43',};
-faultsAttrs[FaultID.FunctionExpression] = {migratable: true, cookBookRef: '46',};
-faultsAttrs[FaultID.LambdaWithTypeParameters] = {migratable: true, cookBookRef: '49',};
-faultsAttrs[FaultID.ClassExpression] = {migratable: true, cookBookRef: '50',};
-faultsAttrs[FaultID.ImplementsClass] = {cookBookRef: '51',};
-faultsAttrs[FaultID.MethodReassignment] = {cookBookRef: '52',};
-faultsAttrs[FaultID.TypeAssertion] = {migratable: true, cookBookRef: '53',};
-faultsAttrs[FaultID.JsxElement] = {cookBookRef: '54',};
-faultsAttrs[FaultID.UnaryArithmNotNumber] = {cookBookRef: '55',};
-faultsAttrs[FaultID.DeleteOperator] = {cookBookRef: '59',};
-faultsAttrs[FaultID.TypeQuery] = {cookBookRef: '60',};
-faultsAttrs[FaultID.InstanceofUnsupported] = {cookBookRef: '65',};
-faultsAttrs[FaultID.InOperator] = {cookBookRef: '66',};
-faultsAttrs[FaultID.DestructuringAssignment] = {migratable: true, cookBookRef: '69',};
-faultsAttrs[FaultID.CommaOperator] = {cookBookRef: '71',};
-faultsAttrs[FaultID.DestructuringDeclaration] = {migratable: true, cookBookRef: '74',};
-faultsAttrs[FaultID.CatchWithUnsupportedType] = {migratable: true, cookBookRef: '79',};
-faultsAttrs[FaultID.ForInStatement] = {cookBookRef: '80',};
-faultsAttrs[FaultID.MappedType] = {cookBookRef: '83',};
-faultsAttrs[FaultID.WithStatement] = {cookBookRef: '84',};
-faultsAttrs[FaultID.ThrowStatement] = {migratable: true, cookBookRef: '87',};
-faultsAttrs[FaultID.LimitedReturnTypeInference] = {migratable: true, cookBookRef: '90',};
-faultsAttrs[FaultID.DestructuringParameter] = {cookBookRef: '91',};
-faultsAttrs[FaultID.LocalFunction] = {migratable: true, cookBookRef: '92',};
-faultsAttrs[FaultID.FunctionContainsThis] = {cookBookRef: '93',};
-faultsAttrs[FaultID.GeneratorFunction] = {cookBookRef: '94',};
-faultsAttrs[FaultID.YieldExpression] = {cookBookRef: '94',};
-faultsAttrs[FaultID.IsOperator] = {cookBookRef: '96',};
-faultsAttrs[FaultID.SpreadOperator] = {cookBookRef: '99',};
-faultsAttrs[FaultID.IntefaceExtendDifProps] = {cookBookRef: '102',};
-faultsAttrs[FaultID.InterfaceMerging] = {cookBookRef: '103',};
-faultsAttrs[FaultID.InterfaceExtendsClass] = {cookBookRef: '104',};
-faultsAttrs[FaultID.ConstructorFuncs] = {cookBookRef: '106',};
-faultsAttrs[FaultID.EnumMemberNonConstInit] = {cookBookRef: '111',};
-faultsAttrs[FaultID.EnumMerging] = {cookBookRef: '113',};
-faultsAttrs[FaultID.NamespaceAsObject] = {cookBookRef: '114',};
-faultsAttrs[FaultID.NonDeclarationInNamespace] = {cookBookRef: '116',};
-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.ExportAssignment] = {cookBookRef: '126',};
-faultsAttrs[FaultID.TypeOnlyExport] = {migratable: true, cookBookRef: '127',};
-faultsAttrs[FaultID.ShorthandAmbientModuleDecl] = {cookBookRef: '128',};
-faultsAttrs[FaultID.WildcardsInModuleName] = {cookBookRef: '129',};
-faultsAttrs[FaultID.UMDModuleDefinition] = {cookBookRef: '130',};
-faultsAttrs[FaultID.NewTarget] = {cookBookRef: '132',};
-faultsAttrs[FaultID.DefiniteAssignment] = {warning: true, cookBookRef: '134',};
-faultsAttrs[FaultID.Prototype] = {cookBookRef: '136',};
-faultsAttrs[FaultID.GlobalThis] = {cookBookRef: '137',};
-faultsAttrs[FaultID.UtilityType] = {cookBookRef: '138',};
-faultsAttrs[FaultID.PropertyDeclOnFunction] = {cookBookRef: '139',};
-faultsAttrs[FaultID.FunctionApplyBindCall] = {cookBookRef: '140',};
-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] = {warning: true, cookBookRef: '148',};
-faultsAttrs[FaultID.ClassAsObject] = {cookBookRef: '149',};
-faultsAttrs[FaultID.ImportAfterStatement] = {cookBookRef: '150',};
-faultsAttrs[FaultID.EsObjectType] = {warning: true, cookBookRef: '151',};
+faultsAttrs[FaultID.LiteralAsPropertyName] = new FaultAttributes(1, true);
+faultsAttrs[FaultID.ComputedPropertyName] = new FaultAttributes(1);
+faultsAttrs[FaultID.SymbolType] = new FaultAttributes(2);
+faultsAttrs[FaultID.PrivateIdentifier] = new FaultAttributes(3, true);
+faultsAttrs[FaultID.DeclWithDuplicateName] = new FaultAttributes(4, true);
+faultsAttrs[FaultID.VarDeclaration] = new FaultAttributes(5, true);
+faultsAttrs[FaultID.AnyType] = new FaultAttributes(8);
+faultsAttrs[FaultID.UnknownType] = new FaultAttributes(8);
+faultsAttrs[FaultID.CallSignature] = new FaultAttributes(14);
+faultsAttrs[FaultID.ConstructorType] = new FaultAttributes(15);
+faultsAttrs[FaultID.MultipleStaticBlocks] = new FaultAttributes(16);
+faultsAttrs[FaultID.IndexMember] = new FaultAttributes(17);
+faultsAttrs[FaultID.IntersectionType] = new FaultAttributes(19);
+faultsAttrs[FaultID.ThisType] = new FaultAttributes(21);
+faultsAttrs[FaultID.ConditionalType] = new FaultAttributes(22);
+faultsAttrs[FaultID.ParameterProperties] = new FaultAttributes(25, true);
+faultsAttrs[FaultID.ConstructorIface] = new FaultAttributes(27);
+faultsAttrs[FaultID.IndexedAccessType] = new FaultAttributes(28);
+faultsAttrs[FaultID.PropertyAccessByIndex] = new FaultAttributes(29, true);
+faultsAttrs[FaultID.StructuralIdentity] = new FaultAttributes(30);
+faultsAttrs[FaultID.GenericCallNoTypeArgs] = new FaultAttributes(34);
+faultsAttrs[FaultID.ObjectLiteralNoContextType] = new FaultAttributes(38);
+faultsAttrs[FaultID.ObjectTypeLiteral] = new FaultAttributes(40);
+faultsAttrs[FaultID.ArrayLiteralNoContextType] = new FaultAttributes(43);
+faultsAttrs[FaultID.FunctionExpression] = new FaultAttributes(46, true);
+faultsAttrs[FaultID.LambdaWithTypeParameters] = new FaultAttributes(49, true);
+faultsAttrs[FaultID.ClassExpression] = new FaultAttributes(50, true);
+faultsAttrs[FaultID.ImplementsClass] = new FaultAttributes(51);
+faultsAttrs[FaultID.MethodReassignment] = new FaultAttributes(52);
+faultsAttrs[FaultID.TypeAssertion] = new FaultAttributes(53, true);
+faultsAttrs[FaultID.JsxElement] = new FaultAttributes(54);
+faultsAttrs[FaultID.UnaryArithmNotNumber] = new FaultAttributes(55);
+faultsAttrs[FaultID.DeleteOperator] = new FaultAttributes(59);
+faultsAttrs[FaultID.TypeQuery] = new FaultAttributes(60);
+faultsAttrs[FaultID.InstanceofUnsupported] = new FaultAttributes(65);
+faultsAttrs[FaultID.InOperator] = new FaultAttributes(66);
+faultsAttrs[FaultID.DestructuringAssignment] = new FaultAttributes(69, true);
+faultsAttrs[FaultID.CommaOperator] = new FaultAttributes(71);
+faultsAttrs[FaultID.DestructuringDeclaration] = new FaultAttributes(74, true);
+faultsAttrs[FaultID.CatchWithUnsupportedType] = new FaultAttributes(79, true);
+faultsAttrs[FaultID.ForInStatement] = new FaultAttributes(80);
+faultsAttrs[FaultID.MappedType] = new FaultAttributes(83);
+faultsAttrs[FaultID.WithStatement] = new FaultAttributes(84);
+faultsAttrs[FaultID.ThrowStatement] = new FaultAttributes(87, true);
+faultsAttrs[FaultID.LimitedReturnTypeInference] = new FaultAttributes(90, true);
+faultsAttrs[FaultID.DestructuringParameter] = new FaultAttributes(91);
+faultsAttrs[FaultID.LocalFunction] = new FaultAttributes(92, true);
+faultsAttrs[FaultID.FunctionContainsThis] = new FaultAttributes(93);
+faultsAttrs[FaultID.GeneratorFunction] = new FaultAttributes(94);
+faultsAttrs[FaultID.YieldExpression] = new FaultAttributes(94);
+faultsAttrs[FaultID.IsOperator] = new FaultAttributes(96);
+faultsAttrs[FaultID.SpreadOperator] = new FaultAttributes(99);
+faultsAttrs[FaultID.IntefaceExtendDifProps] = new FaultAttributes(102);
+faultsAttrs[FaultID.InterfaceMerging] = new FaultAttributes(103);
+faultsAttrs[FaultID.InterfaceExtendsClass] = new FaultAttributes(104);
+faultsAttrs[FaultID.ConstructorFuncs] = new FaultAttributes(106);
+faultsAttrs[FaultID.EnumMemberNonConstInit] = new FaultAttributes(111);
+faultsAttrs[FaultID.EnumMerging] = new FaultAttributes(113);
+faultsAttrs[FaultID.NamespaceAsObject] = new FaultAttributes(114);
+faultsAttrs[FaultID.NonDeclarationInNamespace] = new FaultAttributes(116);
+faultsAttrs[FaultID.ImportFromPath] = new FaultAttributes(119);
+faultsAttrs[FaultID.DefaultImport] = new FaultAttributes(120, true);
+faultsAttrs[FaultID.ImportAssignment] = new FaultAttributes(121);
+faultsAttrs[FaultID.ExportAssignment] = new FaultAttributes(126);
+faultsAttrs[FaultID.ShorthandAmbientModuleDecl] = new FaultAttributes(128);
+faultsAttrs[FaultID.WildcardsInModuleName] = new FaultAttributes(129);
+faultsAttrs[FaultID.UMDModuleDefinition] = new FaultAttributes(130);
+faultsAttrs[FaultID.NewTarget] = new FaultAttributes(132);
+faultsAttrs[FaultID.DefiniteAssignment] = new FaultAttributes(134, false, ProblemSeverity.WARNING);
+faultsAttrs[FaultID.Prototype] = new FaultAttributes(136);
+faultsAttrs[FaultID.GlobalThis] = new FaultAttributes(137, false, ProblemSeverity.WARNING);
+faultsAttrs[FaultID.UtilityType] = new FaultAttributes(138);
+faultsAttrs[FaultID.PropertyDeclOnFunction] = new FaultAttributes(139);
+faultsAttrs[FaultID.FunctionApplyCall] = new FaultAttributes(140);
+faultsAttrs[FaultID.FunctionBind] = new FaultAttributes(140, false, ProblemSeverity.WARNING);
+faultsAttrs[FaultID.ConstAssertion] = new FaultAttributes(142);
+faultsAttrs[FaultID.ImportAssertion] = new FaultAttributes(143);
+faultsAttrs[FaultID.LimitedStdLibApi] = new FaultAttributes(144);
+faultsAttrs[FaultID.StrictDiagnostic] = new FaultAttributes(145);
+faultsAttrs[FaultID.ErrorSuppression] = new FaultAttributes(146);
+faultsAttrs[FaultID.UnsupportedDecorators] = new FaultAttributes(148, false, ProblemSeverity.WARNING);
+faultsAttrs[FaultID.ClassAsObject] = new FaultAttributes(149, false, ProblemSeverity.WARNING);
+faultsAttrs[FaultID.ImportAfterStatement] = new FaultAttributes(150);
+faultsAttrs[FaultID.EsObjectType] = new FaultAttributes(151, false, ProblemSeverity.WARNING);
diff --git a/ets2panda/linter-4.2/src/TestRunner.ts b/ets2panda/linter-4.2/src/TestRunner.ts
index 48bba0088914aaaca063c7e9170706076e08c27a..efc5e7d99de55a194206db04b143b589c9f0d930 100644
--- a/ets2panda/linter-4.2/src/TestRunner.ts
+++ b/ets2panda/linter-4.2/src/TestRunner.ts
@@ -16,8 +16,9 @@
import { TypeScriptLinter } from './TypeScriptLinter';
import { lint } from './LinterRunner';
import { parseCommandLine } from './CommandLineParser';
-import { Autofix } from './Autofixer';
-import Logger from '../utils/logger';
+import type { CommandLineOptions } from './CommandLineOptions';
+import type { Autofix } from './Autofixer';
+import logger from '../utils/logger';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as ts from 'typescript';
@@ -25,7 +26,7 @@ import * as ts from 'typescript';
const TEST_DIR = 'test';
const TAB = ' ';
-const logger = Logger.getLogger();
+const loggerInstance = logger.getLogger();
interface TestNodeInfo {
line: number;
@@ -49,69 +50,57 @@ RESULT_EXT[Mode.RELAX] = '.relax.json';
RESULT_EXT[Mode.AUTOFIX] = '.autofix.json';
const AUTOFIX_CONFIG_EXT = '.autofix.cfg.json';
const AUTOFIX_SKIP_EXT = '.autofix.skip';
-const ARGS_CONFIG_EXT = '.args.json'
+const ARGS_CONFIG_EXT = '.args.json';
const DIFF_EXT = '.diff';
function runTests(testDirs: string[]): number {
let hasComparisonFailures = false;
-
- // Set the IDE mode manually to enable storing information
- // about found bad nodes and also disable the log output.
TypeScriptLinter.ideMode = true;
TypeScriptLinter.testMode = true;
-
- let passed = 0, failed = 0;
-
- // Get tests from test directory
- if (!testDirs?.length) testDirs = [ TEST_DIR ];
+ let passed = 0;
+ let failed = 0;
+ if (!testDirs?.length) {
+ testDirs = [TEST_DIR];
+ }
for (const testDir of testDirs) {
- let testFiles: string[] = fs.readdirSync(testDir)
- .filter((x) => (x.trimEnd().endsWith(ts.Extension.Ts) && !x.trimEnd().endsWith(ts.Extension.Dts)) || x.trimEnd().endsWith(ts.Extension.Tsx));
-
- logger.info(`\nProcessing "${testDir}" directory:\n`);
-
+ const testFiles: string[] = fs.readdirSync(testDir).filter((x) => {
+ return (
+ x.trimEnd().endsWith(ts.Extension.Ts) && !x.trimEnd().endsWith(ts.Extension.Dts) ||
+ x.trimEnd().endsWith(ts.Extension.Tsx)
+ );
+ });
+ loggerInstance.info(`\nProcessing "${testDir}" directory:\n`);
// Run each test in Strict, Autofix, and Relax mode:
for (const testFile of testFiles) {
if (runTest(testDir, testFile, Mode.STRICT)) {
failed++;
hasComparisonFailures = true;
+ } else {
+ passed++;
}
- else passed++;
-
if (runTest(testDir, testFile, Mode.AUTOFIX)) {
failed++;
hasComparisonFailures = true;
+ } else {
+ passed++;
}
- else passed++;
-
if (runTest(testDir, testFile, Mode.RELAX)) {
failed++;
hasComparisonFailures = true;
+ } else {
+ passed++;
}
- else passed++;
}
}
-
- logger.info(`\nSUMMARY: ${passed + failed} total, ${passed} passed or skipped, ${failed} failed.`);
- logger.info((failed > 0) ? '\nTEST FAILED' : '\nTEST SUCCESSFUL');
-
+ loggerInstance.info(`\nSUMMARY: ${passed + failed} total, ${passed} passed or skipped, ${failed} failed.`);
+ loggerInstance.info(failed > 0 ? '\nTEST FAILED' : '\nTEST SUCCESSFUL');
process.exit(hasComparisonFailures ? -1 : 0);
}
-function runTest(testDir: string, testFile: string, mode: Mode): boolean {
- let testFailed = false;
- if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFile + AUTOFIX_SKIP_EXT))) {
- logger.info(`Skipping test ${testFile} (${Mode[mode]} mode)`);
- return false;
- }
- logger.info(`Running test ${testFile} (${Mode[mode]} mode)`);
-
- TypeScriptLinter.initGlobals();
-
+function parseArgs(testDir: string, testFile: string, mode: Mode): CommandLineOptions {
// Configure test parameters and run linter.
const args: string[] = [path.join(testDir, testFile)];
- let argsFileName = path.join(testDir, testFile + ARGS_CONFIG_EXT);
- let currentTestMode = TypeScriptLinter.testMode;
+ const argsFileName = path.join(testDir, testFile + ARGS_CONFIG_EXT);
if (fs.existsSync(argsFileName)) {
const data = fs.readFileSync(argsFileName).toString();
@@ -121,72 +110,84 @@ function runTest(testDir: string, testFile: string, mode: Mode): boolean {
}
}
- if (mode === Mode.RELAX) args.push('--relax');
- else if (mode === Mode.AUTOFIX) {
+ if (mode === Mode.RELAX) {
+ args.push('--relax');
+ } else if (mode === Mode.AUTOFIX) {
args.push('--autofix');
- let autofixCfg = path.join(testDir, testFile + AUTOFIX_CONFIG_EXT);
- if (fs.existsSync(autofixCfg)) args.push(autofixCfg);
- }
- 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 autofixCfg = path.join(testDir, testFile + AUTOFIX_CONFIG_EXT);
+ if (fs.existsSync(autofixCfg)) {
+ args.push(autofixCfg);
+ }
}
- TypeScriptLinter.testMode = currentTestMode;
-
- const resultExt = RESULT_EXT[mode];
- const testResultFileName = testFile + resultExt;
-
- // Get list of bad nodes from the current run.
- const resultNodes: TestNodeInfo[] =
- fileProblems.map(
- (x) => ({
- line: x.line, column: x.column, problem: x.problem,
- autofixable: mode === Mode.AUTOFIX ? x.autofixable : undefined,
- autofix: mode === Mode.AUTOFIX ? x.autofix : undefined,
- suggest: x.suggest,
- rule: x.rule
- })
- );
+ return parseCommandLine(args);
+}
+function compareExpectedAndActual(testDir: string, testFile: string, mode: Mode, resultNodes: TestNodeInfo[]): string {
// Read file with expected test result.
let expectedResult: { nodes: TestNodeInfo[] };
let diff: string = '';
+ const resultExt = RESULT_EXT[mode];
+ const testResultFileName = testFile + resultExt;
try {
const expectedResultFile = fs.readFileSync(path.join(testDir, testResultFileName)).toString();
expectedResult = JSON.parse(expectedResultFile);
-
- if (!expectedResult || !expectedResult.nodes || expectedResult.nodes.length !== resultNodes.length) {
- testFailed = true;
- let expectedResultCount = expectedResult && expectedResult.nodes ? expectedResult.nodes.length : 0;
+ if (!expectedResult?.nodes || expectedResult.nodes.length !== resultNodes.length) {
+ const expectedResultCount = expectedResult?.nodes ? expectedResult.nodes.length : 0;
diff = `Expected count: ${expectedResultCount} vs actual count: ${resultNodes.length}`;
- logger.info(`${TAB}${diff}`);
+ loggerInstance.info(`${TAB}${diff}`);
} else {
diff = expectedAndActualMatch(expectedResult.nodes, resultNodes);
- testFailed = !!diff;
}
-
- if (testFailed) {
- logger.info(`${TAB}Test failed. Expected and actual results differ.`);
+ if (diff) {
+ loggerInstance.info(`${TAB}Test failed. Expected and actual results differ.`);
}
- } catch (error: any) {
- testFailed = true;
- logger.info(`${TAB}Test failed. ${error.message ?? error}`);
+ } catch (error) {
+ loggerInstance.info(`${TAB}Test failed. ` + error);
}
+ return diff;
+}
+function runTest(testDir: string, testFile: string, mode: Mode): boolean {
+ const testFailed = false;
+ if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFile + AUTOFIX_SKIP_EXT))) {
+ loggerInstance.info(`Skipping test ${testFile} (${Mode[mode]} mode)`);
+ return false;
+ }
+ loggerInstance.info(`Running test ${testFile} (${Mode[mode]} mode)`);
+ TypeScriptLinter.initGlobals();
+ const currentTestMode = TypeScriptLinter.testMode;
+ const cmdOptions = parseArgs(testDir, testFile, mode);
+ const result = lint({ cmdOptions: cmdOptions });
+ const fileProblems = result.problemsInfos.get(path.normalize(cmdOptions.inputFiles[0]));
+ if (fileProblems === undefined) {
+ return true;
+ }
+ TypeScriptLinter.testMode = currentTestMode;
+ // Get list of bad nodes from the current run.
+ const resultNodes: TestNodeInfo[] = fileProblems.map((x) => {
+ return {
+ line: x.line,
+ column: x.column,
+ problem: x.problem,
+ autofixable: mode === Mode.AUTOFIX ? x.autofixable : undefined,
+ autofix: mode === Mode.AUTOFIX ? x.autofix : undefined,
+ suggest: x.suggest,
+ rule: x.rule
+ };
+ });
+ // Read file with expected test result.
+ const testResult = compareExpectedAndActual(testDir, testFile, mode, resultNodes);
// Write file with actual test results.
- writeActualResultFile(testDir, testFile, resultExt, resultNodes, diff);
-
+ writeActualResultFile(testDir, testFile, RESULT_EXT[mode], resultNodes, testResult);
return testFailed;
}
function expectedAndActualMatch(expectedNodes: TestNodeInfo[], actualNodes: TestNodeInfo[]): string {
// Compare expected and actual results.
for (let i = 0; i < actualNodes.length; i++) {
- let actual = actualNodes[i];
- let expect = expectedNodes[i];
+ const actual = actualNodes[i];
+ const expect = expectedNodes[i];
if (actual.line !== expect.line || actual.column !== expect.column || actual.problem !== expect.problem) {
return reportDiff(expect, actual);
}
@@ -205,22 +206,38 @@ function expectedAndActualMatch(expectedNodes: TestNodeInfo[], actualNodes: Test
}
function autofixArraysMatch(expected: Autofix[] | undefined, actual: Autofix[] | undefined): boolean {
- if (!expected && !actual) return true;
- if (!(expected && actual) || expected.length !== actual.length) return false;
+ if (!expected && !actual) {
+ return true;
+ }
+ if (!(expected && actual) || expected.length !== actual.length) {
+ return false;
+ }
for (let i = 0; i < actual.length; ++i) {
if (
- actual[i].start !== expected[i].start || actual[i].end !== expected[i].end ||
+ actual[i].start !== expected[i].start ||
+ actual[i].end !== expected[i].end ||
actual[i].replacementText.replace(/\r\n/g, '\n') !== expected[i].replacementText.replace(/\r\n/g, '\n')
- ) return false;
+ ) {
+ return false;
+ }
}
return true;
}
-function writeActualResultFile(testDir: string, testFile: string, resultExt: string, resultNodes: TestNodeInfo[], diff: string) {
+function writeActualResultFile(
+ testDir: string,
+ testFile: string,
+ resultExt: string,
+ resultNodes: TestNodeInfo[],
+ diff: string
+): void {
const actualResultsDir = path.join(testDir, 'results');
- if (!fs.existsSync(actualResultsDir)) fs.mkdirSync(actualResultsDir);
+ if (!fs.existsSync(actualResultsDir)) {
+ fs.mkdirSync(actualResultsDir);
+ }
- const actualResultJSON = JSON.stringify({ nodes: resultNodes }, null, 4);
+ const tabWidth = 4;
+ const actualResultJSON = JSON.stringify({ nodes: resultNodes }, null, tabWidth);
fs.writeFileSync(path.join(actualResultsDir, testFile + resultExt), actualResultJSON);
if (diff) {
@@ -229,17 +246,17 @@ function writeActualResultFile(testDir: string, testFile: string, resultExt: str
}
function reportDiff(expected: TestNodeInfo, actual: TestNodeInfo): string {
- let expectedNode = JSON.stringify({ nodes: [expected] }, null, 4);
- let actualNode = JSON.stringify({ nodes: [actual] }, null, 4);
+ const tabWidth = 4;
+ const expectedNode = JSON.stringify({ nodes: [expected] }, null, tabWidth);
+ const actualNode = JSON.stringify({ nodes: [actual] }, null, tabWidth);
- let diff =
-`Expected:
+ const diff = `Expected:
${expectedNode}
Actual:
${actualNode}`;
- logger.info(diff);
+ loggerInstance.info(diff);
return diff;
}
-runTests(process.argv.slice(2));
\ No newline at end of file
+runTests(process.argv.slice(2));
diff --git a/ets2panda/linter-4.2/src/TypeScriptLinter.ts b/ets2panda/linter-4.2/src/TypeScriptLinter.ts
index 7e11b132f41355e168af9ffa09d4a8438d1d1fae..e699b331f271efd2dc07b22cc82a61693a68116c 100644
--- a/ets2panda/linter-4.2/src/TypeScriptLinter.ts
+++ b/ets2panda/linter-4.2/src/TypeScriptLinter.ts
@@ -15,33 +15,37 @@
import * as ts from 'typescript';
import * as path from 'node:path';
-import { TsUtils, getNodeOrLineEnd, CheckType, isAssignmentOperator } from './Utils';
+import { TsUtils, getNodeOrLineEnd, isAssignmentOperator } from './Utils';
import { FaultID, faultsAttrs } from './Problems';
import { cookBookMsg, cookBookTag } from './CookBookMsg';
import { LinterConfig } from './TypeScriptLinterConfig';
import type { Autofix, AutofixInfoSet } from './Autofixer';
-import * as Autofixer from './Autofixer';
+import * as autofixer from './Autofixer';
import type { ProblemInfo } from './ProblemInfo';
import { ProblemSeverity } from './ProblemSeverity';
-import Logger from '../utils/logger';
+import logger from '../utils/logger';
import type { DiagnosticChecker } from './DiagnosticChecker';
import {
ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE,
TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE,
- LibraryTypeCallDiagnosticChecker
+ LibraryTypeCallDiagnosticChecker,
} from './LibraryTypeCallDiagnosticChecker';
+import { LIMITED_STD_API } from './LimitedStdAPI';
+import { identiferUseInValueContext } from './identiferUseInValueContext';
-const logger = Logger.getLogger();
+const loggerInstance = logger.getLogger();
export function consoleLog(...args: any[]): void {
- if (TypeScriptLinter.ideMode) return;
+ if (TypeScriptLinter.ideMode) {
+ return;
+ }
- let outLine = "";
+ let outLine = '';
for (let k = 0; k < args.length; k++) {
outLine += `${args[k]} `;
}
- logger.info(outLine);
+ loggerInstance.info(outLine);
}
export class TypeScriptLinter {
@@ -50,9 +54,9 @@ export class TypeScriptLinter {
lineCounters: number[] = [];
totalErrorLines: number = 0;
- errorLineNumbersString: string = "";
+ errorLineNumbersString: string = '';
totalWarningLines: number = 0;
- warningLineNumbersString: string = "";
+ warningLineNumbersString: string = '';
problemsInfos: ProblemInfo[] = [];
@@ -61,6 +65,7 @@ export class TypeScriptLinter {
currentErrorLine: number;
currentWarningLine: number;
staticBlocks: Set;
+ walkedComments: Set;
libraryTypeCallDiagnosticChecker: LibraryTypeCallDiagnosticChecker;
private sourceFile?: ts.SourceFile;
@@ -69,26 +74,52 @@ export class TypeScriptLinter {
static testMode: boolean = false;
static initGlobals(): void {
- TypeScriptLinter.filteredDiagnosticMessages = new Set();
+ TypeScriptLinter.filteredDiagnosticMessages =
+ new Set();
+ }
+
+ private initEtsHandlers(): void {
+ /*
+ * some syntax elements are ArkTs-specific and are only implemented inside patched
+ * compiler, so we initialize those handlers if corresponding properties do exist
+ */
+ const etsComponentExpression: ts.SyntaxKind | undefined = (
+ ts.SyntaxKind as any
+ ).EtsComponentExpression;
+ if (etsComponentExpression) {
+ this.handlersMap.set(
+ etsComponentExpression,
+ this.handleEtsComponentExpression
+ );
+ }
+ }
+
+ private initCounters(): void {
+ for (let i = 0; i < FaultID.LAST_ID; i++) {
+ this.nodeCounters[i] = 0;
+ this.lineCounters[i] = 0;
+ }
}
constructor(
- private tsTypeChecker: ts.TypeChecker,
- private autofixesInfo: AutofixInfoSet,
+ private readonly tsTypeChecker: ts.TypeChecker,
+ private readonly autofixesInfo: AutofixInfoSet,
public strictMode: boolean,
public warningsAsErrors: boolean,
- private tscStrictDiagnostics?: Map
+ private readonly tscStrictDiagnostics?: Map
) {
this.tsUtils = new TsUtils(this.tsTypeChecker, TypeScriptLinter.testMode);
this.currentErrorLine = 0;
this.currentWarningLine = 0;
this.staticBlocks = new Set();
- this.libraryTypeCallDiagnosticChecker = new LibraryTypeCallDiagnosticChecker(TypeScriptLinter.filteredDiagnosticMessages);
+ this.walkedComments = new Set();
+ this.libraryTypeCallDiagnosticChecker =
+ new LibraryTypeCallDiagnosticChecker(
+ TypeScriptLinter.filteredDiagnosticMessages
+ );
- for (let i = 0; i < FaultID.LAST_ID; i++) {
- this.nodeCounters[i] = 0;
- this.lineCounters[i] = 0;
- }
+ this.initEtsHandlers();
+ this.initCounters();
}
readonly handlersMap = new Map([
@@ -136,7 +167,6 @@ 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.ExportAssignment, this.handleExportAssignment],
[ts.SyntaxKind.CallExpression, this.handleCallExpression],
[ts.SyntaxKind.MetaProperty, this.handleMetaProperty],
@@ -147,115 +177,155 @@ export class TypeScriptLinter {
[ts.SyntaxKind.GetAccessor, this.handleGetAccessor],
[ts.SyntaxKind.SetAccessor, this.handleSetAccessor],
[ts.SyntaxKind.ConstructSignature, this.handleConstructSignature],
- [ts.SyntaxKind.ExpressionWithTypeArguments, this.handleExpressionWithTypeArguments],
+ [
+ ts.SyntaxKind.ExpressionWithTypeArguments,
+ this.handleExpressionWithTypeArguments,
+ ],
+ [ts.SyntaxKind.ComputedPropertyName, this.handleComputedPropertyName],
]);
- public incrementCounters(
+ private getLineAndCharacterOfNode(
+ node: ts.Node | ts.CommentRange
+ ): ts.LineAndCharacter {
+ const startPos = TsUtils.getStartPos(node);
+ const { line, character } =
+ this.sourceFile!.getLineAndCharacterOfPosition(startPos);
+ // TSC counts lines and columns from zero
+ return { line: line + 1, character: character + 1 };
+ }
+
+ private incrementCountersIdeMode(
+ node: ts.Node | ts.CommentRange,
+ faultId: number,
+ line: number,
+ character: number,
+ autofixable: boolean,
+ autofix?: Autofix[]
+ ): void {
+ const startPos = TsUtils.getStartPos(node);
+ const endPos = TsUtils.getEndPos(node);
+
+ const faultDescr = LinterConfig.nodeDesc[faultId];
+ const faultType = LinterConfig.tsSyntaxKindNames[node.kind];
+
+ const cookBookMsgNum = faultsAttrs[faultId] ? faultsAttrs[faultId].cookBookRef : 0;
+ const cookBookTg = cookBookTag[cookBookMsgNum];
+ const severity = faultsAttrs[faultId]?.severity ?? ProblemSeverity.ERROR;
+ const badNodeInfo: ProblemInfo = {
+ line: line,
+ column: character,
+ endColumn: getNodeOrLineEnd(this.sourceFile!, startPos, endPos, line),
+ start: startPos,
+ end: endPos,
+ type: faultType,
+ severity: severity,
+ problem: FaultID[faultId],
+ suggest: cookBookMsgNum > 0 ? cookBookMsg[cookBookMsgNum] : '',
+ rule: cookBookMsgNum > 0 && cookBookTg !== '' ? cookBookTg : faultDescr ? faultDescr : faultType,
+ ruleTag: cookBookMsgNum,
+ autofixable: autofixable,
+ autofix: autofix,
+ };
+
+ this.problemsInfos.push(badNodeInfo);
+ }
+
+ incrementCounters(
node: ts.Node | ts.CommentRange,
faultId: number,
autofixable: boolean = false,
autofix?: Autofix[]
- ) {
- if (!this.strictMode && faultsAttrs[faultId].migratable)
+ ): void {
+ if (!this.strictMode && faultsAttrs[faultId].migratable) {
// In relax mode skip migratable
return;
-
- 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(startPos);
- ++line;
- ++character;
-
- let faultDescr = LinterConfig.nodeDesc[faultId];
- let faultType = LinterConfig.tsSyntaxKindNames[node.kind];
-
+ const { line, character } = this.getLineAndCharacterOfNode(node);
if (TypeScriptLinter.ideMode) {
- const cookBookMsgNum = faultsAttrs[faultId]
- ? Number(faultsAttrs[faultId].cookBookRef)
- : 0;
- const cookBookTg = cookBookTag[cookBookMsgNum];
- let severity = ProblemSeverity.ERROR;
- if (faultsAttrs[faultId] && faultsAttrs[faultId].warning)
- severity = ProblemSeverity.WARNING;
-
- const badNodeInfo: ProblemInfo = {
- line: line,
- column: character,
- endColumn: getNodeOrLineEnd(this.sourceFile!, startPos, endPos, line),
- start: startPos,
- end: endPos,
- type: faultType,
- severity: severity,
- problem: FaultID[faultId],
- suggest: cookBookMsgNum > 0 ? cookBookMsg[cookBookMsgNum] : "",
- rule:
- cookBookMsgNum > 0 && cookBookTg !== ""
- ? cookBookTg
- : faultDescr
- ? faultDescr
- : faultType,
- ruleTag: cookBookMsgNum,
- autofixable: autofixable,
- autofix: autofix,
- };
-
- this.problemsInfos.push(badNodeInfo);
+ this.incrementCountersIdeMode(
+ node,
+ faultId,
+ line,
+ character,
+ autofixable,
+ autofix
+ );
} else {
- logger.info(
+ const faultDescr = LinterConfig.nodeDesc[faultId];
+ const faultType = LinterConfig.tsSyntaxKindNames[node.kind];
+ loggerInstance.info(
`Warning: ${this.sourceFile!.fileName} (${line}, ${character}): ${
faultDescr ? faultDescr : faultType
}`
);
}
-
this.lineCounters[faultId]++;
-
- if (faultsAttrs[faultId].warning) {
- if (line !== this.currentWarningLine) {
+ switch (faultsAttrs[faultId].severity) {
+ case ProblemSeverity.ERROR: {
+ this.currentErrorLine = line;
+ ++this.totalErrorLines;
+ this.errorLineNumbersString += line + ', ';
+ break;
+ }
+ case ProblemSeverity.WARNING: {
+ if (line === this.currentWarningLine) {
+ break;
+ }
this.currentWarningLine = line;
++this.totalWarningLines;
- this.warningLineNumbersString += line + ", ";
+ this.warningLineNumbersString += line + ', ';
+ break;
}
- } else if (line !== this.currentErrorLine) {
- this.currentErrorLine = line;
- ++this.totalErrorLines;
- this.errorLineNumbersString += line + ", ";
}
}
- private visitTSNode(node: ts.Node): void {
- const self = this;
- visitTSNodeImpl(node);
- function visitTSNodeImpl(node: ts.Node): void {
- if (node === null || node.kind === null) return;
-
- self.totalVisitedNodes++;
-
- if (self.tsUtils.isStructDeclaration(node)) {
- self.handleStructDeclaration(node);
- return;
- }
-
- self.handleComments(node);
+ private forEachNodeInSubtree(
+ node: ts.Node,
+ cb: (n: ts.Node) => void,
+ stopCond?: (n: ts.Node) => boolean
+ ): void {
+ cb.call(this, node);
+ if (stopCond?.call(this, node)) {
+ return;
+ }
- if (LinterConfig.terminalTokens.has(node.kind)) return;
+ ts.forEachChild(node, (child) => {
+ this.forEachNodeInSubtree(child, cb, stopCond);
+ });
+ }
- let incrementedType = LinterConfig.incrementOnlyTokens.get(node.kind);
+ private visitSourceFile(sf: ts.SourceFile): void {
+ const callback = (node: ts.Node): void => {
+ this.totalVisitedNodes++;
+ const incrementedType = LinterConfig.incrementOnlyTokens.get(node.kind);
if (incrementedType !== undefined) {
- self.incrementCounters(node, incrementedType);
+ this.incrementCounters(node, incrementedType);
} else {
- let handler = self.handlersMap.get(node.kind);
+ const handler = this.handlersMap.get(node.kind);
if (handler !== undefined) {
- handler.call(self, node);
+ handler.call(this, node);
}
}
-
- ts.forEachChild(node, visitTSNodeImpl);
- }
+ };
+ const stopCondition = (node: ts.Node): boolean => {
+ if (!node) {
+ return true;
+ }
+ // Skip synthetic constructor in Struct declaration.
+ if (
+ node.parent &&
+ TsUtils.isStructDeclaration(node.parent) &&
+ ts.isConstructorDeclaration(node)
+ ) {
+ return true;
+ }
+ if (LinterConfig.terminalTokens.has(node.kind)) {
+ return true;
+ }
+ return false;
+ };
+ this.forEachNodeInSubtree(sf, callback, stopCondition);
}
private countInterfaceExtendsDifferentPropertyTypes(
@@ -263,7 +333,7 @@ export class TypeScriptLinter {
prop2type: Map,
propName: string,
type: ts.TypeNode | undefined
- ) {
+ ): void {
if (type) {
const methodType = type.getText();
const propType = prop2type.get(propName);
@@ -275,111 +345,139 @@ export class TypeScriptLinter {
}
}
- private countDeclarationsWithDuplicateName(tsNode: ts.Node, tsDeclNode: ts.Node, tsDeclKind?: ts.SyntaxKind
+ private countDeclarationsWithDuplicateName(
+ tsNode: ts.Node,
+ tsDeclNode: ts.Node,
+ tsDeclKind?: ts.SyntaxKind
): void {
- let symbol = this.tsTypeChecker.getSymbolAtLocation(tsNode);
- // If specific declaration kind is provided, check against it.
- // Otherwise, use syntax kind of corresponding declaration node.
- if (!!symbol && this.tsUtils.symbolHasDuplicateName(symbol, tsDeclKind ?? tsDeclNode.kind)) {
+ const symbol = this.tsTypeChecker.getSymbolAtLocation(tsNode);
+
+ /*
+ * If specific declaration kind is provided, check against it.
+ * Otherwise, use syntax kind of corresponding declaration node.
+ */
+ if (
+ !!symbol &&
+ TsUtils.symbolHasDuplicateName(symbol, tsDeclKind ?? tsDeclNode.kind)
+ ) {
this.incrementCounters(tsDeclNode, FaultID.DeclWithDuplicateName);
}
}
+ private static isPrivateIdentifierDuplicateOfIdentifier(
+ ident1: ts.Identifier | ts.PrivateIdentifier,
+ ident2: ts.Identifier | ts.PrivateIdentifier
+ ): boolean {
+ if (ts.isIdentifier(ident1) && ts.isPrivateIdentifier(ident2)) {
+ return ident1.text === ident2.text.substring(1);
+ }
+ if (ts.isIdentifier(ident2) && ts.isPrivateIdentifier(ident1)) {
+ return ident2.text === ident1.text.substring(1);
+ }
+ return false;
+ }
+
+ private static isIdentifierOrPrivateIdentifier(
+ node?: ts.PropertyName
+ ): node is ts.Identifier | ts.PrivateIdentifier {
+ if (!node) {
+ return false;
+ }
+ return ts.isIdentifier(node) || ts.isPrivateIdentifier(node);
+ }
+
private countClassMembersWithDuplicateName(
tsClassDecl: ts.ClassDeclaration
): void {
- for (const tsCurrentMember of tsClassDecl.members) {
+ const nClassMembers = tsClassDecl.members.length;
+ const isNodeReported = new Array(nClassMembers);
+ for (let curIdx = 0; curIdx < nClassMembers; ++curIdx) {
+ const tsCurrentMember = tsClassDecl.members[curIdx];
if (
- !tsCurrentMember.name ||
- !(
- ts.isIdentifier(tsCurrentMember.name) ||
- ts.isPrivateIdentifier(tsCurrentMember.name)
- )
- )
+ !TypeScriptLinter.isIdentifierOrPrivateIdentifier(tsCurrentMember.name)
+ ) {
continue;
-
- for (const tsClassMember of tsClassDecl.members) {
- if (tsCurrentMember === tsClassMember) continue;
-
- if (
- !tsClassMember.name ||
- !(
- ts.isIdentifier(tsClassMember.name) ||
- ts.isPrivateIdentifier(tsClassMember.name)
- )
- )
- continue;
-
+ }
+ if (isNodeReported[curIdx]) {
+ continue;
+ }
+ for (let idx = curIdx + 1; idx < nClassMembers; ++idx) {
+ const tsClassMember = tsClassDecl.members[idx];
if (
- ts.isIdentifier(tsCurrentMember.name) &&
- ts.isPrivateIdentifier(tsClassMember.name) &&
- tsCurrentMember.name.text === tsClassMember.name.text.substring(1)
+ !TypeScriptLinter.isIdentifierOrPrivateIdentifier(tsClassMember.name)
) {
- this.incrementCounters(
- tsCurrentMember,
- FaultID.DeclWithDuplicateName
- );
- break;
+ continue;
}
-
if (
- ts.isPrivateIdentifier(tsCurrentMember.name) &&
- ts.isIdentifier(tsClassMember.name) &&
- tsCurrentMember.name.text.substring(1) === tsClassMember.name.text
+ TypeScriptLinter.isPrivateIdentifierDuplicateOfIdentifier(
+ tsCurrentMember.name,
+ tsClassMember.name
+ )
) {
this.incrementCounters(
tsCurrentMember,
FaultID.DeclWithDuplicateName
);
+ this.incrementCounters(tsClassMember, FaultID.DeclWithDuplicateName);
+ isNodeReported[idx] = true;
break;
}
}
}
}
- private scopeContainsThis(tsNode: ts.Node): boolean {
- let found = false;
+ private static scopeContainsThis(tsNode: ts.Expression | ts.Block): boolean {
+ function scopeContainsThisVisitor(tsNode: ts.Node): boolean {
+ if (tsNode.kind === ts.SyntaxKind.ThisKeyword) {
+ return true;
+ }
- function visitNode(tsNode: ts.Node) {
- // Stop visiting child nodes if finished searching.
- if (found) return;
+ /*
+ * Visit children nodes. Skip any local declaration that defines
+ * its own scope as it needs to be checked separately.
+ */
+ const isClassLike =
+ ts.isClassDeclaration(tsNode) || ts.isClassExpression(tsNode);
+ const isFunctionLike =
+ ts.isFunctionDeclaration(tsNode) || ts.isFunctionExpression(tsNode);
+ const isModuleDecl = ts.isModuleDeclaration(tsNode);
+ if (isClassLike || isFunctionLike || isModuleDecl) {
+ return false;
+ }
- if (tsNode.kind === ts.SyntaxKind.ThisKeyword) {
- found = true;
- return;
+ for (const child of tsNode.getChildren()) {
+ if (scopeContainsThisVisitor(child)) {
+ return true;
+ }
}
- // Visit children nodes. Skip any local declaration that defines
- // its own scope as it needs to be checked separately.
- if (
- !ts.isClassDeclaration(tsNode) &&
- !ts.isClassExpression(tsNode) &&
- !ts.isModuleDeclaration(tsNode) &&
- !ts.isFunctionDeclaration(tsNode) &&
- !ts.isFunctionExpression(tsNode)
- )
- tsNode.forEachChild(visitNode);
+ return false;
}
- visitNode(tsNode);
-
- return found;
+ return scopeContainsThisVisitor(tsNode);
}
- private isPrototypePropertyAccess(tsPropertyAccess: ts.PropertyAccessExpression, propAccessSym: ts.Symbol | undefined,
- baseExprSym: ts.Symbol | undefined, baseExprType: ts.Type): boolean {
+ private isPrototypePropertyAccess(
+ tsPropertyAccess: ts.PropertyAccessExpression,
+ propAccessSym: ts.Symbol | undefined,
+ baseExprSym: ts.Symbol | undefined,
+ baseExprType: ts.Type
+ ): boolean {
if (
!(
ts.isIdentifier(tsPropertyAccess.name) &&
- tsPropertyAccess.name.text === "prototype"
+ tsPropertyAccess.name.text === 'prototype'
)
- )
+ ) {
return false;
+ }
// #13600: Relax prototype check when expression comes from interop.
let curPropAccess: ts.Node = tsPropertyAccess;
while (curPropAccess && ts.isPropertyAccessExpression(curPropAccess)) {
- const baseExprSym = this.tsUtils.trueSymbolAtLocation(curPropAccess.expression);
+ const baseExprSym = this.tsUtils.trueSymbolAtLocation(
+ curPropAccess.expression
+ );
if (this.tsUtils.isLibrarySymbol(baseExprSym)) {
return false;
}
@@ -388,25 +486,30 @@ export class TypeScriptLinter {
if (ts.isIdentifier(curPropAccess) && curPropAccess.text !== 'prototype') {
const type = this.tsTypeChecker.getTypeAtLocation(curPropAccess);
- if (this.tsUtils.isAnyType(type)) {
+ if (TsUtils.isAnyType(type)) {
return false;
}
}
// Check if property symbol is 'Prototype'
- if (this.tsUtils.isPrototypeSymbol(propAccessSym)) return true;
+ if (TsUtils.isPrototypeSymbol(propAccessSym)) {
+ return true;
+ }
// Check if symbol of LHS-expression is Class or Function.
if (
- this.tsUtils.isTypeSymbol(baseExprSym) ||
- this.tsUtils.isFunctionSymbol(baseExprSym)
- )
+ TsUtils.isTypeSymbol(baseExprSym) ||
+ 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 = ...
+ /*
+ * 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 baseExprTypeNode = this.tsTypeChecker.typeToTypeNode(
baseExprType,
undefined,
@@ -415,7 +518,7 @@ export class TypeScriptLinter {
return (
(baseExprTypeNode && ts.isFunctionTypeNode(baseExprTypeNode)) ||
- this.tsUtils.isAnyType(baseExprType)
+ TsUtils.isAnyType(baseExprType)
);
}
@@ -424,21 +527,24 @@ export class TypeScriptLinter {
heritageClauses: ts.NodeArray
): void {
for (const hClause of heritageClauses) {
- if (hClause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
+ if (hClause.token !== ts.SyntaxKind.ExtendsKeyword) {
+ continue;
+ }
const prop2type = new Map();
for (const tsTypeExpr of hClause.types) {
const tsExprType = this.tsTypeChecker.getTypeAtLocation(
tsTypeExpr.expression
);
- if (tsExprType.isClass())
+ if (tsExprType.isClass()) {
this.incrementCounters(node, FaultID.InterfaceExtendsClass);
- else if (tsExprType.isClassOrInterface())
+ } else if (tsExprType.isClassOrInterface()) {
this.lintForInterfaceExtendsDifferentPorpertyTypes(
node,
tsExprType,
prop2type
);
+ }
}
}
}
@@ -450,7 +556,9 @@ export class TypeScriptLinter {
): void {
const props = tsExprType.getProperties();
for (const p of props) {
- if (!p.declarations) continue;
+ if (!p.declarations) {
+ continue;
+ }
const decl: ts.Declaration = p.declarations[0];
if (decl.kind === ts.SyntaxKind.MethodSignature) {
@@ -485,41 +593,50 @@ export class TypeScriptLinter {
}
}
- private handleObjectLiteralExpression(node: ts.Node) {
- let objectLiteralExpr = node as ts.ObjectLiteralExpression;
+ private handleObjectLiteralExpression(node: ts.Node): void {
+ const objectLiteralExpr = node as ts.ObjectLiteralExpression;
// If object literal is a part of destructuring assignment, then don't process it further.
- if (this.tsUtils.isDestructuringAssignmentLHS(objectLiteralExpr)) return;
+ if (TsUtils.isDestructuringAssignmentLHS(objectLiteralExpr)) {
+ return;
+ }
- let objectLiteralType =
+ const objectLiteralType =
this.tsTypeChecker.getContextualType(objectLiteralExpr);
if (
!this.tsUtils.isStructObjectInitializer(objectLiteralExpr) &&
!this.tsUtils.isDynamicLiteralInitializer(objectLiteralExpr) &&
- !this.tsUtils.isObjectLiteralAssignable(objectLiteralType, objectLiteralExpr)
- )
+ !this.tsUtils.isObjectLiteralAssignable(
+ 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.
+ private handleArrayLiteralExpression(node: ts.Node): void {
+ /*
+ * If array literal is a part of destructuring assignment, then
+ * don't process it further.
+ */
if (
- this.tsUtils.isDestructuringAssignmentLHS(
- node as ts.ArrayLiteralExpression
- )
- )
+ TsUtils.isDestructuringAssignmentLHS(node as ts.ArrayLiteralExpression)
+ ) {
return;
+ }
- let arrayLitNode = node as ts.ArrayLiteralExpression;
+ const arrayLitNode = node as ts.ArrayLiteralExpression;
let noContextTypeForArrayLiteral = false;
- // check that array literal consists of inferrable types
- // e.g. there is no element which is untyped object literals
- let arrayLitElements = arrayLitNode.elements;
- for (let element of arrayLitElements) {
+ /*
+ * check that array literal consists of inferrable types
+ * e.g. there is no element which is untyped object literals
+ */
+ const arrayLitElements = arrayLitNode.elements;
+ for (const element of arrayLitElements) {
if (ts.isObjectLiteralExpression(element)) {
- let objectLiteralType = this.tsTypeChecker.getContextualType(element);
+ const objectLiteralType = this.tsTypeChecker.getContextualType(element);
if (
!this.tsUtils.isDynamicLiteralInitializer(arrayLitNode) &&
!this.tsUtils.isObjectLiteralAssignable(objectLiteralType, element)
@@ -530,71 +647,88 @@ export class TypeScriptLinter {
}
}
- if (noContextTypeForArrayLiteral)
+ if (noContextTypeForArrayLiteral) {
this.incrementCounters(node, FaultID.ArrayLiteralNoContextType);
+ }
}
- private handleParameter(node: ts.Node) {
- let tsParam = node as ts.ParameterDeclaration;
+ private handleParameter(node: ts.Node): void {
+ const tsParam = node as ts.ParameterDeclaration;
if (
ts.isArrayBindingPattern(tsParam.name) ||
ts.isObjectBindingPattern(tsParam.name)
- )
+ ) {
this.incrementCounters(node, FaultID.DestructuringParameter);
+ }
- let tsParamMods = tsParam.modifiers;
+ const 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.ReadonlyKeyword) ||
- this.tsUtils.hasModifier(tsParamMods, ts.SyntaxKind.PrivateKeyword))
- )
+ (TsUtils.hasModifier(tsParamMods, ts.SyntaxKind.PublicKeyword) ||
+ TsUtils.hasModifier(tsParamMods, ts.SyntaxKind.ProtectedKeyword) ||
+ TsUtils.hasModifier(tsParamMods, ts.SyntaxKind.ReadonlyKeyword) ||
+ TsUtils.hasModifier(tsParamMods, ts.SyntaxKind.PrivateKeyword))
+ ) {
this.incrementCounters(node, FaultID.ParameterProperties);
+ }
this.handleDecorators(tsParam.decorators);
this.handleDeclarationInferredType(tsParam);
}
- private handleEnumDeclaration(node: ts.Node) {
- let enumNode = node as ts.EnumDeclaration;
+ private handleEnumDeclaration(node: ts.Node): void {
+ const enumNode = node as ts.EnumDeclaration;
this.countDeclarationsWithDuplicateName(enumNode.name, enumNode);
- let enumSymbol = this.tsUtils.trueSymbolAtLocation(enumNode.name);
- if (!enumSymbol) return;
+ const enumSymbol = this.tsUtils.trueSymbolAtLocation(enumNode.name);
+ if (!enumSymbol) {
+ return;
+ }
- let enumDecls = enumSymbol.getDeclarations();
- if (!enumDecls) return;
+ const enumDecls = enumSymbol.getDeclarations();
+ if (!enumDecls) {
+ return;
+ }
- // Since type checker merges all declarations with the same name
- // into one symbol, we need to check that there's more than one
- // enum declaration related to that specific symbol.
- // See 'countDeclarationsWithDuplicateName' method for details.
+ /*
+ * Since type checker merges all declarations with the same name
+ * into one symbol, we need to check that there's more than one
+ * enum declaration related to that specific symbol.
+ * See 'countDeclarationsWithDuplicateName' method for details.
+ */
let enumDeclCount = 0;
for (const decl of enumDecls) {
- if (decl.kind === ts.SyntaxKind.EnumDeclaration) enumDeclCount++;
+ if (decl.kind === ts.SyntaxKind.EnumDeclaration) {
+ enumDeclCount++;
+ }
}
- if (enumDeclCount > 1)
+ if (enumDeclCount > 1) {
this.incrementCounters(node, FaultID.EnumMerging);
+ }
}
- private handleInterfaceDeclaration(node: ts.Node) {
- let interfaceNode = node as ts.InterfaceDeclaration;
- let iSymbol = this.tsUtils.trueSymbolAtLocation(interfaceNode.name);
- let iDecls = iSymbol ? iSymbol.getDeclarations() : null;
+ private handleInterfaceDeclaration(node: ts.Node): void {
+ const interfaceNode = node as ts.InterfaceDeclaration;
+ const iSymbol = this.tsUtils.trueSymbolAtLocation(interfaceNode.name);
+ const iDecls = iSymbol ? iSymbol.getDeclarations() : null;
if (iDecls) {
- // Since type checker merges all declarations with the same name
- // into one symbol, we need to check that there's more than one
- // interface declaration related to that specific symbol.
- // See 'countDeclarationsWithDuplicateName' method for details.
+ /*
+ * Since type checker merges all declarations with the same name
+ * into one symbol, we need to check that there's more than one
+ * interface declaration related to that specific symbol.
+ * See 'countDeclarationsWithDuplicateName' method for details.
+ */
let iDeclCount = 0;
for (const decl of iDecls) {
- if (decl.kind === ts.SyntaxKind.InterfaceDeclaration) iDeclCount++;
+ if (decl.kind === ts.SyntaxKind.InterfaceDeclaration) {
+ iDeclCount++;
+ }
}
- if (iDeclCount > 1)
+ if (iDeclCount > 1) {
this.incrementCounters(node, FaultID.InterfaceMerging);
+ }
}
if (interfaceNode.heritageClauses) {
@@ -604,44 +738,46 @@ export class TypeScriptLinter {
this.countDeclarationsWithDuplicateName(interfaceNode.name, interfaceNode);
}
- private handleThrowStatement(node: ts.Node) {
- let throwStmt = node as ts.ThrowStatement;
- let throwExprType = this.tsTypeChecker.getTypeAtLocation(
+ private handleThrowStatement(node: ts.Node): void {
+ const throwStmt = node as ts.ThrowStatement;
+ const throwExprType = this.tsTypeChecker.getTypeAtLocation(
throwStmt.expression
);
if (
!throwExprType.isClassOrInterface() ||
- !this.tsUtils.isDerivedFrom(throwExprType, CheckType.Error)
+ !this.tsUtils.isOrDerivedFrom(throwExprType, this.tsUtils.isStdErrorType)
) {
this.incrementCounters(node, FaultID.ThrowStatement, false, undefined);
}
}
- private handleForStatement(node: ts.Node) {
- let tsForStmt = node as ts.ForStatement;
- let tsForInit = tsForStmt.initializer;
+ private handleForStatement(node: ts.Node): void {
+ const tsForStmt = node as ts.ForStatement;
+ const tsForInit = tsForStmt.initializer;
if (
tsForInit &&
(ts.isArrayLiteralExpression(tsForInit) ||
ts.isObjectLiteralExpression(tsForInit))
- )
+ ) {
this.incrementCounters(tsForInit, FaultID.DestructuringAssignment);
+ }
}
- private handleForInStatement(node: ts.Node) {
- let tsForInStmt = node as ts.ForInStatement;
- let tsForInInit = tsForInStmt.initializer;
+ private handleForInStatement(node: ts.Node): void {
+ const tsForInStmt = node as ts.ForInStatement;
+ const tsForInInit = tsForInStmt.initializer;
if (
ts.isArrayLiteralExpression(tsForInInit) ||
ts.isObjectLiteralExpression(tsForInInit)
- )
+ ) {
this.incrementCounters(tsForInInit, FaultID.DestructuringAssignment);
+ }
this.incrementCounters(node, FaultID.ForInStatement);
}
- private handleForOfStatement(node: ts.Node) {
- let tsForOfStmt = node as ts.ForOfStatement;
- let tsForOfInit = tsForOfStmt.initializer;
+ private handleForOfStatement(node: ts.Node): void {
+ const tsForOfStmt = node as ts.ForOfStatement;
+ const tsForOfInit = tsForOfStmt.initializer;
if (
ts.isArrayLiteralExpression(tsForOfInit) ||
ts.isObjectLiteralExpression(tsForOfInit)
@@ -650,8 +786,8 @@ export class TypeScriptLinter {
}
}
- private handleImportDeclaration(node: ts.Node) {
- let importDeclNode = node as ts.ImportDeclaration;
+ private handleImportDeclaration(node: ts.Node): void {
+ const importDeclNode = node as ts.ImportDeclaration;
for (const stmt of importDeclNode.parent.statements) {
if (stmt === importDeclNode) {
break;
@@ -661,32 +797,47 @@ export class TypeScriptLinter {
break;
}
}
- let expr1 = importDeclNode.moduleSpecifier;
+ const expr1 = importDeclNode.moduleSpecifier;
if (expr1.kind === ts.SyntaxKind.StringLiteral) {
- if (!importDeclNode.importClause)
+ if (!importDeclNode.importClause) {
this.incrementCounters(node, FaultID.ImportFromPath);
+ }
}
}
- private handlePropertyAccessExpression(node: ts.Node) {
- let propertyAccessNode = node as ts.PropertyAccessExpression;
+ private handlePropertyAccessExpression(node: ts.Node): void {
+ if (ts.isCallExpression(node.parent) && node === node.parent.expression) {
+ return;
+ }
+
+ const propertyAccessNode = node as ts.PropertyAccessExpression;
const exprSym = this.tsUtils.trueSymbolAtLocation(propertyAccessNode);
- const baseExprSym = this.tsUtils.trueSymbolAtLocation(propertyAccessNode.expression);
- const baseExprType = this.tsTypeChecker.getTypeAtLocation(propertyAccessNode.expression);
+ const baseExprSym = this.tsUtils.trueSymbolAtLocation(
+ propertyAccessNode.expression
+ );
+ const baseExprType = this.tsTypeChecker.getTypeAtLocation(
+ propertyAccessNode.expression
+ );
- if (!!baseExprSym && this.tsUtils.symbolHasEsObjectType(baseExprSym)) {
- this.incrementCounters(propertyAccessNode, FaultID.EsObjectType);
- }
- if (this.isPrototypePropertyAccess(propertyAccessNode, exprSym, baseExprSym, baseExprType)) {
+ if (
+ this.isPrototypePropertyAccess(
+ propertyAccessNode,
+ exprSym,
+ baseExprSym,
+ baseExprType
+ )
+ ) {
this.incrementCounters(propertyAccessNode.name, FaultID.Prototype);
}
+ if (!!baseExprSym && TsUtils.symbolHasEsObjectType(baseExprSym)) {
+ this.incrementCounters(propertyAccessNode, FaultID.EsObjectType);
+ }
}
- private handlePropertyAssignmentOrDeclaration(node: ts.Node) {
- let propName = (node as ts.PropertyAssignment | ts.PropertyDeclaration)
+ private handlePropertyAssignmentOrDeclaration(node: ts.Node): void {
+ const propName = (node as ts.PropertyAssignment | ts.PropertyDeclaration)
.name;
-
if (
propName &&
(propName.kind === ts.SyntaxKind.NumericLiteral ||
@@ -694,23 +845,27 @@ export class TypeScriptLinter {
) {
// We can use literals as property names only when creating Record or any interop instances.
let isRecordObjectInitializer = false;
- let isDynamicLiteralInitializer = false;
+ let isLibraryType = false;
+ let isDynamic = false;
if (ts.isPropertyAssignment(node)) {
- let objectLiteralType = this.tsTypeChecker.getContextualType(
- node.parent
- );
- isRecordObjectInitializer =
- !!objectLiteralType &&
- this.tsUtils.isStdRecordType(objectLiteralType);
- isDynamicLiteralInitializer = this.tsUtils.isDynamicLiteralInitializer(
+ const objectLiteralType = this.tsTypeChecker.getContextualType(
node.parent
);
+ if (objectLiteralType) {
+ isRecordObjectInitializer = this.tsUtils.checkTypeSet(
+ objectLiteralType,
+ this.tsUtils.isStdRecordType
+ );
+ isLibraryType = this.tsUtils.isLibraryType(objectLiteralType);
+ }
+ isDynamic =
+ isLibraryType ||
+ this.tsUtils.isDynamicLiteralInitializer(node.parent);
}
-
- if (!isRecordObjectInitializer && !isDynamicLiteralInitializer) {
+ if (!isRecordObjectInitializer && !isDynamic) {
let autofix: Autofix[] | undefined =
- Autofixer.fixLiteralAsPropertyName(node);
- let autofixable = autofix !== undefined;
+ autofixer.fixLiteralAsPropertyName(node);
+ const autofixable = autofix !== undefined;
if (
!this.autofixesInfo.shouldAutofix(node, FaultID.LiteralAsPropertyName)
) {
@@ -724,19 +879,24 @@ export class TypeScriptLinter {
);
}
}
-
if (ts.isPropertyDeclaration(node)) {
const decorators = node.decorators;
this.handleDecorators(decorators);
- this.filterOutDecoratorsDiagnostics(decorators, TsUtils.NON_INITIALIZABLE_PROPERTY_DECORATORS,
- {begin: propName.getStart(), end: propName.getStart()},
- TsUtils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE);
-
+ this.filterOutDecoratorsDiagnostics(
+ decorators,
+ TsUtils.NON_INITIALIZABLE_PROPERTY_DECORATORS,
+ { begin: propName.getStart(), end: propName.getStart() },
+ TsUtils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE
+ );
const classDecorators = node.parent.decorators;
- const propType = (node as ts.PropertyDeclaration).type?.getText();
- this.filterOutDecoratorsDiagnostics(classDecorators, TsUtils.NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS,
- {begin: propName.getStart(), end: propName.getStart()}, TsUtils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE, propType);
-
+ const propType = node.type?.getText();
+ this.filterOutDecoratorsDiagnostics(
+ classDecorators,
+ TsUtils.NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS,
+ { begin: propName.getStart(), end: propName.getStart() },
+ TsUtils.PROPERTY_HAS_NO_INITIALIZER_ERROR_CODE,
+ propType
+ );
this.handleDeclarationInferredType(node);
this.handleDefiniteAssignmentAssertion(node);
}
@@ -745,62 +905,65 @@ export class TypeScriptLinter {
private filterOutDecoratorsDiagnostics(
decorators: readonly ts.Decorator[] | undefined,
expectedDecorators: readonly string[],
- range: {begin: number, end: number},
+ range: { begin: number; end: number },
code: number,
propType?: string
- ) {
+ ): void {
// 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 (
+ 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;
-
+ }
// special case for property of type CustomDialogController of the @CustomDialog-decorated class
- if (expectedDecorators.includes(TsUtils.NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS[0])) {
- return expectedDecorators.includes(decoratorName) && propType === 'CustomDialogController'
+ if (
+ expectedDecorators.includes(
+ TsUtils.NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS[0]
+ )
+ ) {
+ return (
+ expectedDecorators.includes(decoratorName) &&
+ propType === 'CustomDialogController'
+ );
}
- return expectedDecorators.includes(
- decoratorName
- );
+ return expectedDecorators.includes(decoratorName);
})
) {
- let file = path.normalize(this.sourceFile.fileName);
- let tscDiagnostics = this.tscStrictDiagnostics.get(file);
+ const file = path.normalize(this.sourceFile.fileName);
+ const tscDiagnostics = this.tscStrictDiagnostics.get(file);
if (tscDiagnostics) {
- let filteredDiagnostics = tscDiagnostics.filter(
- (val, idx, array) => {
- if (val.code !== code) {
- return true;
- }
-
- if (val.start === undefined) {
- return true;
- }
-
- if (val.start < range.begin) {
- return true;
- }
-
- if (val.start > range.end) {
- return true;
- }
-
- return false;
+ const filteredDiagnostics = tscDiagnostics.filter((val) => {
+ if (val.code !== code) {
+ return true;
}
- );
+ if (val.start === undefined) {
+ return true;
+ }
+ if (val.start < range.begin) {
+ return true;
+ }
+ if (val.start > range.end) {
+ return true;
+ }
+ return false;
+ });
this.tscStrictDiagnostics.set(file, filteredDiagnostics);
}
}
}
}
- private checkInRange(rangesToFilter: { begin: number, end: number }[], pos: number): boolean {
+ private static checkInRange(
+ rangesToFilter: { begin: number; end: number }[],
+ pos: number
+ ): boolean {
for (let i = 0; i < rangesToFilter.length; i++) {
if (pos >= rangesToFilter[i].begin && pos < rangesToFilter[i].end) {
return false;
@@ -809,18 +972,20 @@ export class TypeScriptLinter {
return true;
}
- private filterStrictDiagnostics(filters: { [code: number]: (pos: number) => boolean },
- diagnosticChecker: DiagnosticChecker): boolean {
+ private filterStrictDiagnostics(
+ filters: { [code: number]: (pos: number) => boolean },
+ diagnosticChecker: DiagnosticChecker
+ ): boolean {
if (!this.tscStrictDiagnostics || !this.sourceFile) {
return false;
}
- let file = path.normalize(this.sourceFile.fileName);
- let tscDiagnostics = this.tscStrictDiagnostics.get(file)
+ const file = path.normalize(this.sourceFile.fileName);
+ const tscDiagnostics = this.tscStrictDiagnostics.get(file);
if (!tscDiagnostics) {
return false;
}
- const checkDiagnostic = (val: ts.Diagnostic) => {
+ const checkDiagnostic = (val: ts.Diagnostic): boolean => {
const checkInRange = filters[val.code];
if (!checkInRange) {
return true;
@@ -838,28 +1003,34 @@ export class TypeScriptLinter {
return true;
}
- private handleFunctionExpression(node: ts.Node) {
+ private static isClassLikeOrIface(node: ts.Node): boolean {
+ return ts.isClassLike(node) || ts.isInterfaceDeclaration(node);
+ }
+
+ private handleFunctionExpression(node: ts.Node): void {
const funcExpr = node as ts.FunctionExpression;
- const isGenerator = funcExpr.asteriskToken !== undefined;
- const containsThis = this.scopeContainsThis(funcExpr.body);
- const hasValidContext =
- this.tsUtils.hasPredecessor(funcExpr, ts.isClassLike) ||
- this.tsUtils.hasPredecessor(funcExpr, ts.isInterfaceDeclaration);
const isGeneric =
funcExpr.typeParameters !== undefined &&
funcExpr.typeParameters.length > 0;
- const isCalledRecursively = this.tsUtils.isFunctionCalledRecursively(funcExpr);
+ const isGenerator = funcExpr.asteriskToken !== undefined;
+ const hasThisKeyword = TypeScriptLinter.scopeContainsThis(funcExpr.body);
+ const isCalledRecursively =
+ this.tsUtils.isFunctionCalledRecursively(funcExpr);
const [hasUnfixableReturnType, newRetTypeNode] =
this.handleMissingReturnType(funcExpr);
- const autofixable = !isGeneric && !isGenerator && !containsThis && !hasUnfixableReturnType &&
- !isCalledRecursively;
+ const autofixable =
+ !isGeneric &&
+ !isGenerator &&
+ !hasThisKeyword &&
+ !isCalledRecursively &&
+ !hasUnfixableReturnType;
let autofix: Autofix[] | undefined;
if (
autofixable &&
- this.autofixesInfo.shouldAutofix(node, FaultID.FunctionExpression)
+ this.autofixesInfo.shouldAutofix(funcExpr, FaultID.FunctionExpression)
) {
autofix = [
- Autofixer.fixFunctionExpression(
+ autofixer.fixFunctionExpression(
funcExpr,
funcExpr.parameters,
newRetTypeNode,
@@ -868,7 +1039,7 @@ export class TypeScriptLinter {
];
}
this.incrementCounters(
- node,
+ funcExpr,
FaultID.FunctionExpression,
autofixable,
autofix
@@ -879,22 +1050,28 @@ export class TypeScriptLinter {
if (isGenerator) {
this.incrementCounters(funcExpr, FaultID.GeneratorFunction);
}
- if (containsThis && !hasValidContext) {
- this.incrementCounters(funcExpr, FaultID.FunctionContainsThis);
+ if (
+ !TypeScriptLinter.hasPredecessor(
+ funcExpr,
+ TypeScriptLinter.isClassLikeOrIface
+ )
+ ) {
+ this.reportThisKeywordsInScope(funcExpr.body);
}
if (hasUnfixableReturnType) {
this.incrementCounters(funcExpr, FaultID.LimitedReturnTypeInference);
}
}
- private handleArrowFunction(node: ts.Node) {
+ private handleArrowFunction(node: ts.Node): void {
const arrowFunc = node as ts.ArrowFunction;
- const containsThis = this.scopeContainsThis(arrowFunc.body);
- const hasValidContext =
- this.tsUtils.hasPredecessor(arrowFunc, ts.isClassLike) ||
- this.tsUtils.hasPredecessor(arrowFunc, ts.isInterfaceDeclaration);
- if (containsThis && !hasValidContext) {
- this.incrementCounters(arrowFunc, FaultID.FunctionContainsThis);
+ if (
+ !TypeScriptLinter.hasPredecessor(
+ arrowFunc,
+ TypeScriptLinter.isClassLikeOrIface
+ )
+ ) {
+ this.reportThisKeywordsInScope(arrowFunc.body);
}
const contextType = this.tsTypeChecker.getContextualType(arrowFunc);
if (!(contextType && this.tsUtils.isLibraryType(contextType))) {
@@ -907,64 +1084,85 @@ export class TypeScriptLinter {
}
}
- private handleClassExpression(node: ts.Node) {
- let tsClassExpr = node as ts.ClassExpression;
+ private handleClassExpression(node: ts.Node): void {
+ const tsClassExpr = node as ts.ClassExpression;
this.incrementCounters(node, FaultID.ClassExpression);
this.handleDecorators(tsClassExpr.decorators);
}
- private handleFunctionDeclaration(node: ts.Node) {
- let tsFunctionDeclaration = node as ts.FunctionDeclaration;
- if (!tsFunctionDeclaration.type)
+ private handleFunctionDeclaration(node: ts.Node): void {
+ const tsFunctionDeclaration = node as ts.FunctionDeclaration;
+ if (!tsFunctionDeclaration.type) {
this.handleMissingReturnType(tsFunctionDeclaration);
- if (tsFunctionDeclaration.name)
- this.countDeclarationsWithDuplicateName(tsFunctionDeclaration.name, tsFunctionDeclaration);
-
- if (
- tsFunctionDeclaration.body &&
- this.scopeContainsThis(tsFunctionDeclaration.body)
- )
- this.incrementCounters(node, FaultID.FunctionContainsThis);
-
- if (
- !ts.isSourceFile(tsFunctionDeclaration.parent) &&
- !ts.isModuleBlock(tsFunctionDeclaration.parent)
- )
+ }
+ if (tsFunctionDeclaration.name) {
+ this.countDeclarationsWithDuplicateName(
+ tsFunctionDeclaration.name,
+ tsFunctionDeclaration
+ );
+ }
+ if (tsFunctionDeclaration.body) {
+ this.reportThisKeywordsInScope(tsFunctionDeclaration.body);
+ }
+ const parent = tsFunctionDeclaration.parent;
+ if (!ts.isSourceFile(parent) && !ts.isModuleBlock(parent)) {
this.incrementCounters(tsFunctionDeclaration, FaultID.LocalFunction);
-
- if (tsFunctionDeclaration.asteriskToken)
+ }
+ if (tsFunctionDeclaration.asteriskToken) {
this.incrementCounters(node, FaultID.GeneratorFunction);
+ }
}
- private handleMissingReturnType(funcLikeDecl: ts.FunctionLikeDeclaration | ts.MethodSignature): [boolean, ts.TypeNode | undefined] {
+ private handleMissingReturnType(
+ funcLikeDecl: ts.FunctionLikeDeclaration | ts.MethodSignature
+ ): [boolean, ts.TypeNode | undefined] {
// Note: Return type can't be inferred for function without body.
if (ts.isMethodSignature(funcLikeDecl) || !funcLikeDecl.body) {
// Ambient flag is not exposed, so we apply dirty hack to make it visible
- const isAmbientDeclaration = !!(funcLikeDecl.flags & (ts.NodeFlags as any).Ambient);
- const isSignature = ts.isMethodSignature(funcLikeDecl);
- if ((isSignature || isAmbientDeclaration) && !funcLikeDecl.type) {
- this.incrementCounters(funcLikeDecl, FaultID.LimitedReturnTypeInference);
+ const isAmbientDeclaration = !!(
+ funcLikeDecl.flags & (ts.NodeFlags as any).Ambient
+ );
+ if (
+ (ts.isMethodSignature(funcLikeDecl) || isAmbientDeclaration) &&
+ !funcLikeDecl.type
+ ) {
+ this.incrementCounters(
+ funcLikeDecl,
+ FaultID.LimitedReturnTypeInference
+ );
}
return [false, undefined];
}
+
+ return this.tryAutofixMissingReturnType(funcLikeDecl);
+ }
+
+ private tryAutofixMissingReturnType(
+ funcLikeDecl: ts.FunctionLikeDeclaration
+ ): [boolean, ts.TypeNode | undefined] {
+ if (!funcLikeDecl.body) {
+ return [false, undefined];
+ }
+
let autofixable = false;
let autofix: Autofix[] | undefined;
let newRetTypeNode: ts.TypeNode | undefined;
- let isFuncExpr = ts.isFunctionExpression(funcLikeDecl);
+ const isFuncExpr = ts.isFunctionExpression(funcLikeDecl);
- // Currently, ArkTS can't infer return type of function, when expression
- // in the return statement is a call to a function or method whose return
- // value type is omitted. In that case, we attempt to prepare an autofix.
+ /*
+ * Currently, ArkTS can't infer return type of function, when expression
+ * in the return statement is a call to a function or method whose return
+ * value type is omitted. In that case, we attempt to prepare an autofix.
+ */
let hasLimitedRetTypeInference = this.hasLimitedTypeInferenceFromReturnExpr(
funcLikeDecl.body
);
-
- let tsSignature =
+ const tsSignature =
this.tsTypeChecker.getSignatureFromDeclaration(funcLikeDecl);
if (tsSignature) {
- let tsRetType = this.tsTypeChecker.getReturnTypeOfSignature(tsSignature);
-
- if (!tsRetType || this.tsUtils.isUnsupportedType(tsRetType)) {
+ const tsRetType =
+ this.tsTypeChecker.getReturnTypeOfSignature(tsSignature);
+ if (!tsRetType || TsUtils.isUnsupportedType(tsRetType)) {
hasLimitedRetTypeInference = true;
} else if (hasLimitedRetTypeInference) {
newRetTypeNode = this.tsTypeChecker.typeToTypeNode(
@@ -980,21 +1178,24 @@ export class TypeScriptLinter {
FaultID.LimitedReturnTypeInference
)
) {
- autofix = [Autofixer.fixReturnType(funcLikeDecl, newRetTypeNode)];
+ autofix = [autofixer.fixReturnType(funcLikeDecl, newRetTypeNode)];
}
}
}
}
- // Don't report here if in function expression context.
- // See handleFunctionExpression for details.
- if (hasLimitedRetTypeInference && !isFuncExpr)
+ /*
+ * Don't report here if in function expression context.
+ * See handleFunctionExpression for details.
+ */
+ if (hasLimitedRetTypeInference && !isFuncExpr) {
this.incrementCounters(
funcLikeDecl,
FaultID.LimitedReturnTypeInference,
autofixable,
autofix
);
+ }
return [hasLimitedRetTypeInference && !newRetTypeNode, newRetTypeNode];
}
@@ -1003,36 +1204,36 @@ export class TypeScriptLinter {
funBody: ts.ConciseBody
): boolean {
let hasLimitedTypeInference = false;
- const self = this;
- function visitNode(tsNode: ts.Node): void {
- if (hasLimitedTypeInference) return;
+ const callback = (node: ts.Node): void => {
+ if (hasLimitedTypeInference) {
+ return;
+ }
if (
- ts.isReturnStatement(tsNode) &&
- tsNode.expression &&
- self.tsUtils.isCallToFunctionWithOmittedReturnType(
- self.tsUtils.unwrapParenthesized(tsNode.expression)
+ ts.isReturnStatement(node) &&
+ node.expression &&
+ this.tsUtils.isCallToFunctionWithOmittedReturnType(
+ TsUtils.unwrapParenthesized(node.expression)
)
) {
hasLimitedTypeInference = true;
- return;
}
-
- // Visit children nodes. Don't traverse other nested function-like declarations.
- if (
- !ts.isFunctionDeclaration(tsNode) &&
- !ts.isFunctionExpression(tsNode) &&
- !ts.isMethodDeclaration(tsNode) &&
- !ts.isAccessor(tsNode) &&
- !ts.isArrowFunction(tsNode)
- )
- tsNode.forEachChild(visitNode);
- }
+ };
+ // Don't traverse other nested function-like declarations.
+ const stopCondition = (node: ts.Node): boolean => {
+ return (
+ ts.isFunctionDeclaration(node) ||
+ ts.isFunctionExpression(node) ||
+ ts.isMethodDeclaration(node) ||
+ ts.isAccessor(node) ||
+ ts.isArrowFunction(node)
+ );
+ };
if (ts.isBlock(funBody)) {
- visitNode(funBody);
+ this.forEachNodeInSubtree(funBody, callback, stopCondition);
} else {
- const tsExpr = this.tsUtils.unwrapParenthesized(funBody);
+ const tsExpr = TsUtils.unwrapParenthesized(funBody);
hasLimitedTypeInference =
this.tsUtils.isCallToFunctionWithOmittedReturnType(tsExpr);
}
@@ -1040,9 +1241,9 @@ export class TypeScriptLinter {
return hasLimitedTypeInference;
}
- private handlePrefixUnaryExpression(node: ts.Node) {
- let tsUnaryArithm = node as ts.PrefixUnaryExpression;
- let tsUnaryOp = tsUnaryArithm.operator;
+ private handlePrefixUnaryExpression(node: ts.Node): void {
+ const tsUnaryArithm = node as ts.PrefixUnaryExpression;
+ const tsUnaryOp = tsUnaryArithm.operator;
if (
tsUnaryOp === ts.SyntaxKind.PlusToken ||
tsUnaryOp === ts.SyntaxKind.MinusToken ||
@@ -1061,160 +1262,158 @@ export class TypeScriptLinter {
!this.tsUtils.isIntegerConstantValue(
tsUnaryArithm.operand as ts.NumericLiteral
))
- )
+ ) {
this.incrementCounters(node, FaultID.UnaryArithmNotNumber);
+ }
}
}
- private handleBinaryExpression(node: ts.Node) {
- let tsBinaryExpr = node as ts.BinaryExpression;
- let tsLhsExpr = tsBinaryExpr.left;
- let tsRhsExpr = tsBinaryExpr.right;
-
+ private handleBinaryExpression(node: ts.Node): void {
+ const tsBinaryExpr = node as ts.BinaryExpression;
+ const tsLhsExpr = tsBinaryExpr.left;
+ const tsRhsExpr = tsBinaryExpr.right;
if (isAssignmentOperator(tsBinaryExpr.operatorToken)) {
- if (
- ts.isObjectLiteralExpression(tsLhsExpr) ||
- ts.isArrayLiteralExpression(tsLhsExpr)
- )
- this.incrementCounters(node, FaultID.DestructuringAssignment);
-
- if (ts.isPropertyAccessExpression(tsLhsExpr)) {
- const tsLhsSymbol = this.tsUtils.trueSymbolAtLocation(tsLhsExpr);
- const tsLhsBaseSymbol = this.tsUtils.trueSymbolAtLocation(
- tsLhsExpr.expression
- );
- if (tsLhsSymbol && (tsLhsSymbol.flags & ts.SymbolFlags.Method)) {
- this.incrementCounters(tsLhsExpr, FaultID.MethodReassignment);
- }
+ this.processBinaryAssignment(node, tsLhsExpr);
+ }
+ const leftOperandType = this.tsTypeChecker.getTypeAtLocation(tsLhsExpr);
+ const rightOperandType = this.tsTypeChecker.getTypeAtLocation(tsRhsExpr);
+ const typeNode = this.tsUtils.getVariableDeclarationTypeNode(tsLhsExpr);
+ switch (tsBinaryExpr.operatorToken.kind) {
+ // FaultID.BitOpWithWrongType - removed as rule #61
+ case ts.SyntaxKind.CommaToken:
+ this.processBinaryComma(tsBinaryExpr);
+ break;
+ case ts.SyntaxKind.InstanceOfKeyword:
+ this.processBinaryInstanceOf(node, tsLhsExpr, leftOperandType);
+ break;
+ case ts.SyntaxKind.InKeyword:
+ this.incrementCounters(tsBinaryExpr.operatorToken, FaultID.InOperator);
+ break;
+ case ts.SyntaxKind.EqualsToken:
if (
- this.tsUtils.isMethodAssignment(tsLhsSymbol) &&
- tsLhsBaseSymbol &&
- (tsLhsBaseSymbol.flags & ts.SymbolFlags.Function) !== 0
- )
- this.incrementCounters(tsLhsExpr, FaultID.PropertyDeclOnFunction);
- }
+ this.tsUtils.needToDeduceStructuralIdentity(
+ leftOperandType,
+ rightOperandType,
+ tsRhsExpr
+ )
+ ) {
+ this.incrementCounters(tsBinaryExpr, FaultID.StructuralIdentity);
+ }
+ this.handleEsObjectAssignment(tsBinaryExpr, typeNode, tsRhsExpr);
+ break;
+ default:
}
+ }
- let leftOperandType = this.tsTypeChecker.getTypeAtLocation(tsLhsExpr);
- let rightOperandType = this.tsTypeChecker.getTypeAtLocation(tsRhsExpr);
-
- if (tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.PlusToken) {
- 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 if (
- this.tsUtils.isNumberType(leftOperandType) &&
- this.tsUtils.isNumberType(rightOperandType)
- )
- return;
- else if (
- this.tsUtils.isStringLikeType(leftOperandType) ||
- this.tsUtils.isStringLikeType(rightOperandType)
- )
- return;
- } else if (
- tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.AmpersandToken ||
- tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.BarToken ||
- tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.CaretToken ||
- tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.LessThanLessThanToken ||
- tsBinaryExpr.operatorToken.kind ===
- ts.SyntaxKind.GreaterThanGreaterThanToken ||
- tsBinaryExpr.operatorToken.kind ===
- ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken
+ private processBinaryAssignment(
+ node: ts.Node,
+ tsLhsExpr: ts.Expression
+ ): void {
+ if (
+ ts.isObjectLiteralExpression(tsLhsExpr) ||
+ ts.isArrayLiteralExpression(tsLhsExpr)
) {
+ this.incrementCounters(node, FaultID.DestructuringAssignment);
+ }
+ if (ts.isPropertyAccessExpression(tsLhsExpr)) {
+ const tsLhsSymbol = this.tsUtils.trueSymbolAtLocation(tsLhsExpr);
+ const tsLhsBaseSymbol = this.tsUtils.trueSymbolAtLocation(
+ tsLhsExpr.expression
+ );
+ if (tsLhsSymbol && tsLhsSymbol.flags & ts.SymbolFlags.Method) {
+ this.incrementCounters(tsLhsExpr, FaultID.MethodReassignment);
+ }
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;
- let tsParentNode = tsExprNode.parent;
- while (
- tsParentNode &&
- tsParentNode.kind === ts.SyntaxKind.BinaryExpression
+ TsUtils.isMethodAssignment(tsLhsSymbol) &&
+ tsLhsBaseSymbol &&
+ (tsLhsBaseSymbol.flags & ts.SymbolFlags.Function) !== 0
) {
- tsExprNode = tsParentNode;
- tsParentNode = tsExprNode.parent;
+ this.incrementCounters(tsLhsExpr, FaultID.PropertyDeclOnFunction);
}
+ }
+ }
- if (tsParentNode && tsParentNode.kind === ts.SyntaxKind.ForStatement) {
- const tsForNode = tsParentNode as ts.ForStatement;
- if (
- tsExprNode === tsForNode.initializer ||
- tsExprNode === tsForNode.incrementor
- )
- return;
- }
- this.incrementCounters(node, FaultID.CommaOperator);
- } else if (
- tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword
+ private processBinaryComma(tsBinaryExpr: ts.BinaryExpression): void {
+ // CommaOpertor is allowed in 'for' statement initalizer and incrementor
+ let tsExprNode: ts.Node = tsBinaryExpr;
+ let tsParentNode = tsExprNode.parent;
+ while (
+ tsParentNode &&
+ tsParentNode.kind === ts.SyntaxKind.BinaryExpression
) {
- const leftExpr = this.tsUtils.unwrapParenthesized(tsBinaryExpr.left);
- const leftSymbol = this.tsUtils.trueSymbolAtLocation(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 (tsLhsExpr.kind === ts.SyntaxKind.ThisKeyword) {
- return;
- }
-
+ tsExprNode = tsParentNode;
+ tsParentNode = tsExprNode.parent;
+ }
+ if (tsParentNode && tsParentNode.kind === ts.SyntaxKind.ForStatement) {
+ const tsForNode = tsParentNode as ts.ForStatement;
if (
- this.tsUtils.isPrimitiveType(leftOperandType) ||
- ts.isTypeNode(leftExpr) ||
- this.tsUtils.isTypeSymbol(leftSymbol)
+ tsExprNode === tsForNode.initializer ||
+ tsExprNode === tsForNode.incrementor
) {
- this.incrementCounters(node, FaultID.InstanceofUnsupported);
- }
- } else if (tsBinaryExpr.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
- if (this.tsUtils.needToDeduceStructuralIdentity(leftOperandType, rightOperandType, tsRhsExpr)) {
- this.incrementCounters(tsBinaryExpr, FaultID.StructuralIdentity);
+ return;
}
+ }
+ this.incrementCounters(tsBinaryExpr as ts.Node, FaultID.CommaOperator);
+ }
- const typeNode = this.tsUtils.getVariableDeclarationTypeNode(tsLhsExpr);
- this.handleEsObjectAssignment(tsBinaryExpr, typeNode, tsRhsExpr);
+ private processBinaryInstanceOf(
+ node: ts.Node,
+ tsLhsExpr: ts.Expression,
+ leftOperandType: ts.Type
+ ): void {
+ const leftExpr = TsUtils.unwrapParenthesized(tsLhsExpr);
+ const leftSymbol = this.tsUtils.trueSymbolAtLocation(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 (tsLhsExpr.kind === ts.SyntaxKind.ThisKeyword) {
+ return;
+ }
+
+ if (
+ TsUtils.isPrimitiveType(leftOperandType) ||
+ ts.isTypeNode(leftExpr) ||
+ TsUtils.isTypeSymbol(leftSymbol)
+ ) {
+ this.incrementCounters(node, FaultID.InstanceofUnsupported);
}
}
- private handleVariableDeclarationList(node: ts.Node) {
- let varDeclFlags = ts.getCombinedNodeFlags(node);
- if (!(varDeclFlags & (ts.NodeFlags.Let | ts.NodeFlags.Const)))
+ private handleVariableDeclarationList(node: ts.Node): void {
+ const varDeclFlags = ts.getCombinedNodeFlags(node);
+ if (!(varDeclFlags & (ts.NodeFlags.Let | ts.NodeFlags.Const))) {
this.incrementCounters(node, FaultID.VarDeclaration);
+ }
}
private handleVariableDeclaration(node: ts.Node): void {
- let tsVarDecl = node as ts.VariableDeclaration;
+ const tsVarDecl = node as ts.VariableDeclaration;
if (
ts.isArrayBindingPattern(tsVarDecl.name) ||
ts.isObjectBindingPattern(tsVarDecl.name)
- )
+ ) {
this.incrementCounters(node, FaultID.DestructuringDeclaration);
+ }
{
// Check variable declaration for duplicate name.
- const visitBindingPatternNames = (tsBindingName: ts.BindingName) => {
- if (ts.isIdentifier(tsBindingName))
+ const visitBindingPatternNames = (
+ tsBindingName: ts.BindingName
+ ): void => {
+ if (ts.isIdentifier(tsBindingName)) {
// The syntax kind of the declaration is defined here by the parent of 'BindingName' node.
- this.countDeclarationsWithDuplicateName(tsBindingName, tsBindingName, tsBindingName.parent.kind);
- else {
+ this.countDeclarationsWithDuplicateName(
+ tsBindingName,
+ tsBindingName,
+ tsBindingName.parent.kind
+ );
+ } else {
for (const tsBindingElem of tsBindingName.elements) {
- if (ts.isOmittedExpression(tsBindingElem)) continue;
+ if (ts.isOmittedExpression(tsBindingElem)) {
+ continue;
+ }
visitBindingPatternNames(tsBindingElem.name);
}
@@ -1225,10 +1424,16 @@ export class TypeScriptLinter {
}
if (tsVarDecl.type && tsVarDecl.initializer) {
- let tsVarInit = tsVarDecl.initializer;
- let tsVarType = this.tsTypeChecker.getTypeAtLocation(tsVarDecl.type);
- let tsInitType = this.tsTypeChecker.getTypeAtLocation(tsVarInit);
- if (this.tsUtils.needToDeduceStructuralIdentity(tsVarType, tsInitType, tsVarInit)) {
+ const tsVarInit = tsVarDecl.initializer;
+ const tsVarType = this.tsTypeChecker.getTypeAtLocation(tsVarDecl.type);
+ const tsInitType = this.tsTypeChecker.getTypeAtLocation(tsVarInit);
+ if (
+ this.tsUtils.needToDeduceStructuralIdentity(
+ tsVarType,
+ tsInitType,
+ tsVarInit
+ )
+ ) {
this.incrementCounters(tsVarDecl, FaultID.StructuralIdentity);
}
}
@@ -1239,10 +1444,13 @@ export class TypeScriptLinter {
}
private handleEsObjectDelaration(node: ts.VariableDeclaration): void {
- const isDeclaredESObject = !!node.type && this.tsUtils.isEsObjectType(node.type);
- const initalizerTypeNode = node.initializer && this.tsUtils.getVariableDeclarationTypeNode(node.initializer);
- const isInitializedWithESObject = !!initalizerTypeNode && this.tsUtils.isEsObjectType(initalizerTypeNode);
- const isLocal = this.tsUtils.isInsideBlock(node);
+ const isDeclaredESObject = !!node.type && TsUtils.isEsObjectType(node.type);
+ const initalizerTypeNode =
+ node.initializer &&
+ this.tsUtils.getVariableDeclarationTypeNode(node.initializer);
+ const isInitializedWithESObject =
+ !!initalizerTypeNode && TsUtils.isEsObjectType(initalizerTypeNode);
+ const isLocal = TsUtils.isInsideBlock(node);
if ((isDeclaredESObject || isInitializedWithESObject) && !isLocal) {
this.incrementCounters(node, FaultID.EsObjectType);
return;
@@ -1253,36 +1461,50 @@ export class TypeScriptLinter {
}
}
- private handleEsObjectAssignment(node: ts.Node, nodeDeclType: ts.TypeNode | undefined, initializer: ts.Node): void {
+ private handleEsObjectAssignment(
+ node: ts.Node,
+ nodeDeclType: ts.TypeNode | undefined,
+ initializer: ts.Node
+ ): void {
const isTypeAnnotated = !!nodeDeclType;
- const isDeclaredESObject = !!nodeDeclType && this.tsUtils.isEsObjectType(nodeDeclType);
- const initalizerTypeNode = this.tsUtils.getVariableDeclarationTypeNode(initializer);
- const isInitializedWithESObject = !!initalizerTypeNode && this.tsUtils.isEsObjectType(initalizerTypeNode);
+ const isDeclaredESObject =
+ !!nodeDeclType && TsUtils.isEsObjectType(nodeDeclType);
+ const initalizerTypeNode =
+ this.tsUtils.getVariableDeclarationTypeNode(initializer);
+ const isInitializedWithESObject =
+ !!initalizerTypeNode && TsUtils.isEsObjectType(initalizerTypeNode);
if (isTypeAnnotated && !isDeclaredESObject && isInitializedWithESObject) {
this.incrementCounters(node, FaultID.EsObjectType);
return;
}
- if (isDeclaredESObject && !this.tsUtils.isValueAssignableToESObject(initializer)) {
+ if (
+ isDeclaredESObject &&
+ !this.tsUtils.isValueAssignableToESObject(initializer)
+ ) {
this.incrementCounters(node, FaultID.EsObjectType);
}
}
- 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 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) {
+ private handleCatchClause(node: ts.Node): void {
+ const 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 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?.type) {
let autofix: Autofix[] | undefined;
if (
this.autofixesInfo.shouldAutofix(
tsCatch,
FaultID.CatchWithUnsupportedType
)
- )
- autofix = [Autofixer.dropTypeOnVarDecl(tsCatch.variableDeclaration)];
+ ) {
+ autofix = [autofixer.dropTypeOnVarDecl(tsCatch.variableDeclaration)];
+ }
this.incrementCounters(
node,
FaultID.CatchWithUnsupportedType,
@@ -1292,20 +1514,26 @@ export class TypeScriptLinter {
}
}
- private handleClassDeclaration(node: ts.Node) {
- let tsClassDecl = node as ts.ClassDeclaration;
+ private handleClassDeclaration(node: ts.Node): void {
+ const tsClassDecl = node as ts.ClassDeclaration;
this.staticBlocks.clear();
- if (tsClassDecl.name)
+ if (tsClassDecl.name) {
this.countDeclarationsWithDuplicateName(tsClassDecl.name, tsClassDecl);
+ }
this.countClassMembersWithDuplicateName(tsClassDecl);
- const visitHClause = (hClause: ts.HeritageClause) => {
+ const visitHClause = (hClause: ts.HeritageClause): void => {
for (const tsTypeExpr of hClause.types) {
- const tsExprType = this.tsTypeChecker.getTypeAtLocation(tsTypeExpr.expression);
- if (tsExprType.isClass() && hClause.token === ts.SyntaxKind.ImplementsKeyword) {
+ const tsExprType = this.tsTypeChecker.getTypeAtLocation(
+ tsTypeExpr.expression
+ );
+ if (
+ tsExprType.isClass() &&
+ hClause.token === ts.SyntaxKind.ImplementsKeyword
+ ) {
this.incrementCounters(tsTypeExpr, FaultID.ImplementsClass);
}
}
@@ -1323,87 +1551,101 @@ export class TypeScriptLinter {
this.handleDecorators(tsClassDecl.decorators);
}
- private handleModuleDeclaration(node: ts.Node) {
- let tsModuleDecl = node as ts.ModuleDeclaration;
+ private handleModuleDeclaration(node: ts.Node): void {
+ const tsModuleDecl = node as ts.ModuleDeclaration;
this.countDeclarationsWithDuplicateName(tsModuleDecl.name, tsModuleDecl);
- let tsModuleBody = tsModuleDecl.body;
- let tsModifiers = tsModuleDecl.modifiers; // TSC 4.2 doesn't have 'ts.getModifiers()' method
+ const tsModuleBody = tsModuleDecl.body;
if (tsModuleBody) {
if (ts.isModuleBlock(tsModuleBody)) {
- for (const tsModuleStmt of tsModuleBody.statements) {
- switch (tsModuleStmt.kind) {
- case ts.SyntaxKind.VariableStatement:
- case ts.SyntaxKind.FunctionDeclaration:
- case ts.SyntaxKind.ClassDeclaration:
- case ts.SyntaxKind.InterfaceDeclaration:
- case ts.SyntaxKind.TypeAliasDeclaration:
- case ts.SyntaxKind.EnumDeclaration:
- case ts.SyntaxKind.ExportDeclaration:
- break;
- // Nested namespace declarations are prohibited
- // but there is no cookbook recipe for it!
- case ts.SyntaxKind.ModuleDeclaration:
- break;
- default:
- this.incrementCounters(
- tsModuleStmt,
- FaultID.NonDeclarationInNamespace
- );
- break;
- }
- }
+ this.handleModuleBlock(tsModuleBody);
}
}
if (
!(tsModuleDecl.flags & ts.NodeFlags.Namespace) &&
- this.tsUtils.hasModifier(tsModifiers, ts.SyntaxKind.DeclareKeyword)
+ TsUtils.hasModifier(tsModuleDecl.modifiers, ts.SyntaxKind.DeclareKeyword)
) {
this.incrementCounters(tsModuleDecl, FaultID.ShorthandAmbientModuleDecl);
}
if (
ts.isStringLiteral(tsModuleDecl.name) &&
- tsModuleDecl.name.text.includes("*")
- )
+ tsModuleDecl.name.text.includes('*')
+ ) {
this.incrementCounters(tsModuleDecl, FaultID.WildcardsInModuleName);
+ }
}
- private handleTypeAliasDeclaration(node: ts.Node) {
- let tsTypeAlias = node as ts.TypeAliasDeclaration;
+ private handleModuleBlock(moduleBlock: ts.ModuleBlock): void {
+ for (const tsModuleStmt of moduleBlock.statements) {
+ switch (tsModuleStmt.kind) {
+ case ts.SyntaxKind.VariableStatement:
+ case ts.SyntaxKind.FunctionDeclaration:
+ case ts.SyntaxKind.ClassDeclaration:
+ case ts.SyntaxKind.InterfaceDeclaration:
+ case ts.SyntaxKind.TypeAliasDeclaration:
+ case ts.SyntaxKind.EnumDeclaration:
+ case ts.SyntaxKind.ExportDeclaration:
+ break;
+
+ /*
+ * Nested namespace declarations are prohibited
+ * but there is no cookbook recipe for it!
+ */
+ case ts.SyntaxKind.ModuleDeclaration:
+ break;
+ default:
+ this.incrementCounters(
+ tsModuleStmt,
+ FaultID.NonDeclarationInNamespace
+ );
+ break;
+ }
+ }
+ }
+
+ private handleTypeAliasDeclaration(node: ts.Node): void {
+ const tsTypeAlias = node as ts.TypeAliasDeclaration;
this.countDeclarationsWithDuplicateName(tsTypeAlias.name, tsTypeAlias);
}
- private handleImportClause(node: ts.Node) {
- let tsImportClause = node as ts.ImportClause;
+ private handleImportClause(node: ts.Node): void {
+ const tsImportClause = node as ts.ImportClause;
if (tsImportClause.name) {
- this.countDeclarationsWithDuplicateName(tsImportClause.name, tsImportClause);
+ this.countDeclarationsWithDuplicateName(
+ tsImportClause.name,
+ tsImportClause
+ );
}
if (
tsImportClause.namedBindings &&
ts.isNamedImports(tsImportClause.namedBindings)
) {
- let nonDefaultSpecs: ts.ImportSpecifier[] = [];
+ const 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 (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(
+ autofixer.fixDefaultImport(
tsImportClause,
defaultSpec,
nonDefaultSpecs
),
];
+ }
this.incrementCounters(
defaultSpec,
FaultID.DefaultImport,
@@ -1412,41 +1654,37 @@ export class TypeScriptLinter {
);
}
}
-
- 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 importSpec = node as ts.ImportSpecifier;
+ private handleImportSpecifier(node: ts.Node): void {
+ const importSpec = node as ts.ImportSpecifier;
this.countDeclarationsWithDuplicateName(importSpec.name, importSpec);
}
- private handleNamespaceImport(node: ts.Node) {
- let tsNamespaceImport = node as ts.NamespaceImport;
- this.countDeclarationsWithDuplicateName(tsNamespaceImport.name, tsNamespaceImport);
+ private handleNamespaceImport(node: ts.Node): void {
+ const tsNamespaceImport = node as ts.NamespaceImport;
+ this.countDeclarationsWithDuplicateName(
+ tsNamespaceImport.name,
+ tsNamespaceImport
+ );
}
- private handleTypeAssertionExpression(node: ts.Node) {
- let tsTypeAssertion = node as ts.TypeAssertion;
- if (tsTypeAssertion.type.getText() === "const")
+ private handleTypeAssertionExpression(node: ts.Node): void {
+ const tsTypeAssertion = node as ts.TypeAssertion;
+ if (tsTypeAssertion.type.getText() === 'const') {
this.incrementCounters(tsTypeAssertion, FaultID.ConstAssertion);
- else
+ } else {
this.incrementCounters(node, FaultID.TypeAssertion, true, [
- Autofixer.fixTypeAssertion(tsTypeAssertion),
+ autofixer.fixTypeAssertion(tsTypeAssertion),
]);
+ }
}
private handleMethodDeclaration(node: ts.Node): void {
const tsMethodDecl = node as ts.MethodDeclaration;
- const hasThis = this.scopeContainsThis(tsMethodDecl);
let isStatic = false;
if (tsMethodDecl.modifiers) {
- for (let mod of tsMethodDecl.modifiers) {
+ for (const mod of tsMethodDecl.modifiers) {
if (mod.kind === ts.SyntaxKind.StaticKeyword) {
isStatic = true;
break;
@@ -1454,53 +1692,70 @@ export class TypeScriptLinter {
}
}
- if (isStatic && hasThis) {
- this.incrementCounters(node, FaultID.FunctionContainsThis);
+ if (tsMethodDecl.body && isStatic) {
+ this.reportThisKeywordsInScope(tsMethodDecl.body);
}
- if (!tsMethodDecl.type) this.handleMissingReturnType(tsMethodDecl);
+ if (!tsMethodDecl.type) {
+ this.handleMissingReturnType(tsMethodDecl);
+ }
- if (tsMethodDecl.asteriskToken)
+ if (tsMethodDecl.asteriskToken) {
this.incrementCounters(node, FaultID.GeneratorFunction);
+ }
this.handleDecorators(tsMethodDecl.decorators);
- this.filterOutDecoratorsDiagnostics(tsMethodDecl.decorators, TsUtils.NON_RETURN_FUNCTION_DECORATORS,
- {begin: tsMethodDecl.parameters.end, end: tsMethodDecl.body?.getStart() ?? tsMethodDecl.parameters.end},
- TsUtils.FUNCTION_HAS_NO_RETURN_ERROR_CODE);
+ this.filterOutDecoratorsDiagnostics(
+ tsMethodDecl.decorators,
+ TsUtils.NON_RETURN_FUNCTION_DECORATORS,
+ {
+ begin: tsMethodDecl.parameters.end,
+ end: tsMethodDecl.body?.getStart() ?? tsMethodDecl.parameters.end,
+ },
+ TsUtils.FUNCTION_HAS_NO_RETURN_ERROR_CODE
+ );
}
private handleMethodSignature(node: ts.MethodSignature): void {
- const tsMethodSign = node as ts.MethodSignature;
+ const tsMethodSign = node;
if (!tsMethodSign.type) {
this.handleMissingReturnType(tsMethodSign);
}
}
- private handleIdentifier(node: ts.Node) {
- let tsIdentifier = node as ts.Identifier;
- let tsIdentSym = this.tsUtils.trueSymbolAtLocation(tsIdentifier);
+ private handleIdentifier(node: ts.Node): void {
+ const tsIdentifier = node as ts.Identifier;
+ const tsIdentSym = this.tsUtils.trueSymbolAtLocation(tsIdentifier);
+
if (!tsIdentSym) {
return;
}
+
if (
(tsIdentSym.flags & ts.SymbolFlags.Module) !== 0 &&
(tsIdentSym.flags & ts.SymbolFlags.Transient) !== 0 &&
tsIdentifier.text === 'globalThis'
) {
- this.incrementCounters(tsIdentifier, FaultID.GlobalThis);
+ this.incrementCounters(node, FaultID.GlobalThis);
} else {
this.checkLimitedStdLib(tsIdentifier, tsIdentSym);
this.handleRestrictedValues(tsIdentifier, tsIdentSym);
}
}
- private isAllowedClassValueContext(tsIdentifier: ts.Identifier, tsIdentSym: ts.Symbol): boolean {
+ private isAllowedClassValueContext(tsIdentifier: ts.Identifier): boolean {
let ctx: ts.Node = tsIdentifier;
- while (ts.isPropertyAccessExpression(ctx.parent) || ts.isQualifiedName(ctx.parent)) {
+ while (
+ ts.isPropertyAccessExpression(ctx.parent) ||
+ ts.isQualifiedName(ctx.parent)
+ ) {
ctx = ctx.parent;
}
- if (ts.isPropertyAssignment(ctx.parent) && ts.isObjectLiteralExpression(ctx.parent.parent)) {
+ if (
+ ts.isPropertyAssignment(ctx.parent) &&
+ ts.isObjectLiteralExpression(ctx.parent.parent)
+ ) {
ctx = ctx.parent.parent;
}
if (ts.isArrowFunction(ctx.parent) && ctx.parent.body === ctx) {
@@ -1508,32 +1763,54 @@ export class TypeScriptLinter {
}
if (ts.isCallExpression(ctx.parent) || ts.isNewExpression(ctx.parent)) {
- let callee = ctx.parent.expression;
- if (callee !== ctx && this.tsUtils.hasLibraryType(callee)) {
+ const callee = ctx.parent.expression;
+ const isAny = TsUtils.isAnyType(
+ this.tsTypeChecker.getTypeAtLocation(callee)
+ );
+ const isDynamic = isAny || this.tsUtils.hasLibraryType(callee);
+ if (callee !== ctx && isDynamic) {
return true;
}
}
return false;
}
- private handleRestrictedValues(tsIdentifier: ts.Identifier, tsIdentSym: ts.Symbol) {
- const illegalValues = ts.SymbolFlags.ConstEnum | ts.SymbolFlags.RegularEnum | ts.SymbolFlags.ValueModule | ts.SymbolFlags.Class;
-
- // 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.
+ private handleRestrictedValues(
+ tsIdentifier: ts.Identifier,
+ tsIdentSym: ts.Symbol
+ ): void {
+ const illegalValues =
+ ts.SymbolFlags.ConstEnum |
+ ts.SymbolFlags.RegularEnum |
+ ts.SymbolFlags.ValueModule |
+ ts.SymbolFlags.Class;
+
+ /*
+ * 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 ((tsIdentSym.flags & ts.SymbolFlags.ValueModule) !== 0) {
- if (!!tsIdentSym && this.tsUtils.symbolHasDuplicateName(tsIdentSym, ts.SyntaxKind.ModuleDeclaration)) {
+ if (
+ !!tsIdentSym &&
+ TsUtils.symbolHasDuplicateName(
+ tsIdentSym,
+ ts.SyntaxKind.ModuleDeclaration
+ )
+ ) {
return;
}
}
- if ((tsIdentSym.flags & illegalValues) === 0 || this.tsUtils.isStruct(tsIdentSym) ||
- !this.identiferUseInValueContext(tsIdentifier, tsIdentSym)) {
+ if (
+ (tsIdentSym.flags & illegalValues) === 0 ||
+ TsUtils.isStruct(tsIdentSym) ||
+ !identiferUseInValueContext(tsIdentifier, tsIdentSym)
+ ) {
return;
}
if ((tsIdentSym.flags & ts.SymbolFlags.Class) !== 0) {
- if (this.isAllowedClassValueContext(tsIdentifier, tsIdentSym)) {
+ if (this.isAllowedClassValueContext(tsIdentifier)) {
return;
}
}
@@ -1546,74 +1823,72 @@ export class TypeScriptLinter {
}
}
- private identiferUseInValueContext(
- ident: ts.Identifier, tsSym: ts.Symbol
- ) {
- // If identifier is the right-most name of Property Access chain or Qualified name,
- // or it's a separate identifier expression, then identifier is being referenced as an value.
- let qualifiedStart: ts.Node = ident;
- while (ts.isPropertyAccessExpression(qualifiedStart.parent) || ts.isQualifiedName(qualifiedStart.parent)) {
- qualifiedStart = qualifiedStart.parent;
- }
- let parent = qualifiedStart.parent;
- return !(
- // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery)
- (ts.isTypeNode(parent) && !ts.isTypeOfExpression(parent)) ||
- // ElementAccess is allowed for enum types
- (ts.isElementAccessExpression(parent) &&
- (parent as ts.ElementAccessExpression).expression === ident && (tsSym.flags & ts.SymbolFlags.Enum)) ||
- ts.isExpressionWithTypeArguments(parent) ||
- ts.isExportAssignment(parent) ||
- ts.isExportSpecifier(parent) ||
- ts.isMetaProperty(parent) ||
- ts.isImportClause(parent) ||
- ts.isClassLike(parent) ||
- ts.isInterfaceDeclaration(parent) ||
- ts.isModuleDeclaration(parent) ||
- ts.isEnumDeclaration(parent) ||
- ts.isNamespaceImport(parent) ||
- ts.isImportSpecifier(parent) ||
- ts.isImportEqualsDeclaration(parent) ||
- (ts.isQualifiedName(qualifiedStart) && ident !== qualifiedStart.right) ||
- (ts.isPropertyAccessExpression(qualifiedStart) &&
- ident !== qualifiedStart.name) ||
- (ts.isNewExpression(qualifiedStart.parent) &&
- qualifiedStart === qualifiedStart.parent.expression) ||
- (ts.isBinaryExpression(qualifiedStart.parent) &&
- qualifiedStart.parent.operatorToken.kind ===
- ts.SyntaxKind.InstanceOfKeyword)
+ private static isEnumPropAccess(
+ ident: ts.Identifier,
+ tsSym: ts.Symbol,
+ context: ts.Node
+ ): boolean {
+ return (
+ ts.isElementAccessExpression(context) &&
+ !!(tsSym.flags & ts.SymbolFlags.Enum) &&
+ (context.expression === ident ||
+ (ts.isPropertyAccessExpression(context.expression) &&
+ context.expression.name === ident))
);
}
- private handleElementAccessExpression(node: ts.Node) {
+ private isElementAcessAllowed(type: ts.Type): boolean {
+ if (type.isUnion()) {
+ for (const t of type.types) {
+ if (!this.isElementAcessAllowed(t)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ const typeNode = this.tsTypeChecker.typeToTypeNode(
+ type,
+ undefined,
+ ts.NodeBuilderFlags.None
+ );
+
+ return (
+ this.tsUtils.isLibraryType(type) ||
+ TsUtils.isAnyType(type) ||
+ this.tsUtils.isOrDerivedFrom(type, this.tsUtils.isArray) ||
+ this.tsUtils.isOrDerivedFrom(type, TsUtils.isTuple) ||
+ this.tsUtils.isOrDerivedFrom(type, this.tsUtils.isStdRecordType) ||
+ TsUtils.isEnumType(type) ||
+ // we allow EsObject here beacuse it is reported later using FaultId.EsObjectType
+ TsUtils.isEsObjectType(typeNode)
+ );
+ }
+
+ private handleElementAccessExpression(node: ts.Node): void {
const tsElementAccessExpr = node as ts.ElementAccessExpression;
- const tsElemAccessBaseExprType = this.tsTypeChecker.getTypeAtLocation(
+ const tsElementAccessExprSymbol = this.tsUtils.trueSymbolAtLocation(
tsElementAccessExpr.expression
);
- const tsElemAccessBaseExprTypeNode = this.tsTypeChecker.typeToTypeNode(
- tsElemAccessBaseExprType,
- undefined,
- ts.NodeBuilderFlags.None
+ const tsElemAccessBaseExprType = TsUtils.getNonNullableType(
+ this.tsUtils.getTypeOrTypeConstraintAtLocation(
+ tsElementAccessExpr.expression
+ )
);
- const checkClassOrInterface = tsElemAccessBaseExprType.isClassOrInterface() &&
- !this.tsUtils.isGenericArrayType(tsElemAccessBaseExprType) &&
- !this.tsUtils.isDerivedFrom(tsElemAccessBaseExprType, CheckType.Array);
- const checkThisOrSuper = this.tsUtils.isThisOrSuperExpr(tsElementAccessExpr.expression) &&
- !this.tsUtils.isDerivedFrom(tsElemAccessBaseExprType, CheckType.Array);
if (
- !this.tsUtils.isLibraryType(tsElemAccessBaseExprType) &&
- !this.tsUtils.isTypedArray(tsElemAccessBaseExprTypeNode) &&
- ( checkClassOrInterface ||
- this.tsUtils.isObjectLiteralType(tsElemAccessBaseExprType) || checkThisOrSuper)
+ // unnamed types do not have symbol, so need to check that explicitly
+ !this.tsUtils.isLibrarySymbol(tsElementAccessExprSymbol) &&
+ !ts.isArrayLiteralExpression(tsElementAccessExpr.expression) &&
+ !this.isElementAcessAllowed(tsElemAccessBaseExprType)
) {
- let autofix = Autofixer.fixPropertyAccessByIndex(node);
+ let autofix = autofixer.fixPropertyAccessByIndex(node);
const autofixable = autofix !== undefined;
if (
!this.autofixesInfo.shouldAutofix(node, FaultID.PropertyAccessByIndex)
- )
+ ) {
autofix = undefined;
-
+ }
this.incrementCounters(
node,
FaultID.PropertyAccessByIndex,
@@ -1627,72 +1902,68 @@ export class TypeScriptLinter {
}
}
- private handleEnumMember(node: ts.Node) {
- let tsEnumMember = node as ts.EnumMember;
- let tsEnumMemberType = this.tsTypeChecker.getTypeAtLocation(tsEnumMember);
- let constVal = this.tsTypeChecker.getConstantValue(tsEnumMember);
+ private handleEnumMember(node: ts.Node): void {
+ const tsEnumMember = node as ts.EnumMember;
+ const tsEnumMemberType = this.tsTypeChecker.getTypeAtLocation(tsEnumMember);
+ const constVal = this.tsTypeChecker.getConstantValue(tsEnumMember);
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 =
+ const enumDecl = tsEnumMember.parent;
+ const firstEnumMember = enumDecl.members[0];
+ const 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
+ const 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" &&
+ typeof constVal === 'string' &&
firstElewmVal !== undefined &&
- typeof firstElewmVal === "string"
- )
+ typeof firstElewmVal === 'string'
+ ) {
return;
+ }
if (
constVal !== undefined &&
- typeof constVal === "number" &&
+ typeof constVal === 'number' &&
firstElewmVal !== undefined &&
- typeof firstElewmVal === "number"
- )
+ typeof firstElewmVal === 'number'
+ ) {
return;
+ }
if (firstEnumMemberType !== tsEnumMemberType) {
this.incrementCounters(node, FaultID.EnumMemberNonConstInit);
}
}
- private handleExportDeclaration(node: ts.Node) {
- let tsExportDecl = node as ts.ExportDeclaration;
- 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 handleExportAssignment(node: ts.Node) {
+ private handleExportAssignment(node: ts.Node): void {
const exportAssignment = node as ts.ExportAssignment;
if (exportAssignment.isExportEquals) {
this.incrementCounters(node, FaultID.ExportAssignment);
}
}
- private handleCallExpression(node: ts.Node) {
- let tsCallExpr = node as ts.CallExpression;
+ private handleCallExpression(node: ts.Node): void {
+ const tsCallExpr = node as ts.CallExpression;
const calleeSym = this.tsUtils.trueSymbolAtLocation(tsCallExpr.expression);
- const calleeType = this.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression);
const callSignature = this.tsTypeChecker.getResolvedSignature(tsCallExpr);
this.handleImportCall(tsCallExpr);
this.handleRequireCall(tsCallExpr);
- if (!!calleeSym) {
- if (this.tsUtils.symbolHasEsObjectType(calleeSym)) {
+ // NOTE: Keep handleFunctionApplyBindPropCall above handleGenericCallWithNoTypeArgs here!!!
+ if (calleeSym !== undefined) {
+ if (TsUtils.symbolHasEsObjectType(calleeSym)) {
this.incrementCounters(tsCallExpr, FaultID.EsObjectType);
}
// need to process Symbol call separatey in order to not report two times when using Symbol API
@@ -1700,30 +1971,42 @@ export class TypeScriptLinter {
this.incrementCounters(tsCallExpr, FaultID.SymbolType);
}
}
- if (!!callSignature) {
+ if (callSignature !== undefined) {
if (!this.tsUtils.isLibrarySymbol(calleeSym)) {
this.handleGenericCallWithNoTypeArgs(tsCallExpr, callSignature);
}
this.handleStructIdentAndUndefinedInArgs(tsCallExpr, callSignature);
}
- this.handleLibraryTypeCall(tsCallExpr, calleeType);
+ this.handleLibraryTypeCall(tsCallExpr);
+
+ if (
+ ts.isPropertyAccessExpression(tsCallExpr.expression) &&
+ this.tsUtils.hasEsObjectType(tsCallExpr.expression.expression)
+ ) {
+ this.incrementCounters(node, FaultID.EsObjectType);
+ }
}
- private handleImportCall(tsCallExpr: ts.CallExpression) {
+ private handleEtsComponentExpression(node: ts.Node): void {
+ // for all the checks we make EtsComponentExpression is compatible with the CallExpression
+ const etsComponentExpression = node as ts.CallExpression;
+ this.handleLibraryTypeCall(etsComponentExpression);
+ }
+
+ private handleImportCall(tsCallExpr: ts.CallExpression): void {
if (tsCallExpr.expression.kind === ts.SyntaxKind.ImportKeyword) {
- // relax rule#133 "arkts-no-runtime-import"
+ // relax rule#133 'arkts-no-runtime-import'
const tsArgs = tsCallExpr.arguments;
- if (tsArgs.length > 1 && ts.isObjectLiteralExpression(tsArgs[1])) {
- let objLitExpr = tsArgs[1] as ts.ObjectLiteralExpression;
+ const objLitExpr = tsArgs[1];
+ if (!!objLitExpr && ts.isObjectLiteralExpression(objLitExpr)) {
for (const tsProp of objLitExpr.properties) {
if (
- ts.isPropertyAssignment(tsProp) ||
- ts.isShorthandPropertyAssignment(tsProp)
+ (ts.isPropertyAssignment(tsProp) ||
+ ts.isShorthandPropertyAssignment(tsProp)) &&
+ tsProp.name.getText() === 'assert'
) {
- if (tsProp.name.getText() === "assert") {
- this.incrementCounters(tsProp, FaultID.ImportAssertion);
- break;
- }
+ this.incrementCounters(tsProp, FaultID.ImportAssertion);
+ break;
}
}
}
@@ -1733,23 +2016,29 @@ export class TypeScriptLinter {
private handleRequireCall(tsCallExpr: ts.CallExpression): void {
if (
ts.isIdentifier(tsCallExpr.expression) &&
- tsCallExpr.expression.text === "require" &&
+ tsCallExpr.expression.text === 'require' &&
ts.isVariableDeclaration(tsCallExpr.parent)
) {
- let tsType = this.tsTypeChecker.getTypeAtLocation(tsCallExpr.expression);
+ const tsType = this.tsTypeChecker.getTypeAtLocation(
+ tsCallExpr.expression
+ );
if (
- this.tsUtils.isInterfaceType(tsType) &&
- tsType.symbol.name === "NodeRequire"
- )
+ TsUtils.isInterfaceType(tsType) &&
+ tsType.symbol.name === 'NodeRequire'
+ ) {
this.incrementCounters(tsCallExpr.parent, FaultID.ImportAssignment);
+ }
}
}
- private handleGenericCallWithNoTypeArgs(callLikeExpr: ts.CallExpression | ts.NewExpression, callSignature: ts.Signature): void {
- let tsSyntaxKind = ts.isNewExpression(callLikeExpr)
+ private handleGenericCallWithNoTypeArgs(
+ callLikeExpr: ts.CallExpression | ts.NewExpression,
+ callSignature: ts.Signature
+ ): void {
+ const tsSyntaxKind = ts.isNewExpression(callLikeExpr)
? ts.SyntaxKind.Constructor
: ts.SyntaxKind.FunctionDeclaration;
- let signDecl = this.tsTypeChecker.signatureToSignatureDeclaration(
+ const signDecl = this.tsTypeChecker.signatureToSignatureDeclaration(
callSignature,
tsSyntaxKind,
undefined,
@@ -1758,15 +2047,18 @@ export class TypeScriptLinter {
);
if (signDecl?.typeArguments) {
- let resolvedTypeArgs = signDecl.typeArguments;
- let startTypeArg = callLikeExpr.typeArguments?.length ?? 0;
+ const resolvedTypeArgs = signDecl.typeArguments;
+ const startTypeArg = callLikeExpr.typeArguments?.length ?? 0;
for (let i = startTypeArg; i < resolvedTypeArgs.length; ++i) {
const typeNode = resolvedTypeArgs[i];
- // if compiler infers 'unknown' type there are 2 possible cases:
- // 1. Compiler unable to infer type from arguments and use 'unknown'
- // 2. Compiler infer 'unknown' from arguments
- // We report error in both cases. It is ok because we cannot use 'unknown'
- // in ArkTS and already have separate check for it.
+
+ /*
+ * if compiler infers 'unknown' type there are 2 possible cases:
+ * 1. Compiler unable to infer type from arguments and use 'unknown'
+ * 2. Compiler infer 'unknown' from arguments
+ * We report error in both cases. It is ok because we cannot use 'unknown'
+ * in ArkTS and already have separate check for it.
+ */
if (typeNode.kind === ts.SyntaxKind.UnknownKeyword) {
this.incrementCounters(callLikeExpr, FaultID.GenericCallNoTypeArgs);
break;
@@ -1775,7 +2067,10 @@ export class TypeScriptLinter {
}
}
- private handleStructIdentAndUndefinedInArgs(tsCallOrNewExpr: ts.CallExpression | ts.NewExpression, callSignature: ts.Signature): void {
+ private handleStructIdentAndUndefinedInArgs(
+ tsCallOrNewExpr: ts.CallExpression | ts.NewExpression,
+ callSignature: ts.Signature
+ ): void {
if (!tsCallOrNewExpr.arguments) {
return;
}
@@ -1784,15 +2079,22 @@ export class TypeScriptLinter {
argIndex < tsCallOrNewExpr.arguments.length;
++argIndex
) {
- let tsArg = tsCallOrNewExpr.arguments[argIndex];
- let tsArgType = this.tsTypeChecker.getTypeAtLocation(tsArg);
- if (!tsArgType) continue;
+ const tsArg = tsCallOrNewExpr.arguments[argIndex];
+ const tsArgType = this.tsTypeChecker.getTypeAtLocation(tsArg);
+ if (!tsArgType) {
+ continue;
+ }
- let paramIndex = argIndex < callSignature.parameters.length ? argIndex : callSignature.parameters.length - 1;
- let tsParamSym = callSignature.parameters[paramIndex];
- if (!tsParamSym) continue;
+ const paramIndex =
+ argIndex < callSignature.parameters.length ?
+ argIndex :
+ callSignature.parameters.length - 1;
+ const tsParamSym = callSignature.parameters[paramIndex];
+ if (!tsParamSym) {
+ continue;
+ }
- let tsParamDecl = tsParamSym.valueDeclaration;
+ const tsParamDecl = tsParamSym.valueDeclaration;
if (tsParamDecl && ts.isParameter(tsParamDecl)) {
let tsParamType = this.tsTypeChecker.getTypeOfSymbolAtLocation(
tsParamSym,
@@ -1800,14 +2102,23 @@ export class TypeScriptLinter {
);
if (
tsParamDecl.dotDotDotToken &&
- this.tsUtils.isGenericArrayType(tsParamType) &&
+ TsUtils.isGenericArrayType(tsParamType) &&
tsParamType.typeArguments
- )
+ ) {
tsParamType = tsParamType.typeArguments[0];
+ }
- if (!tsParamType) continue;
+ if (!tsParamType) {
+ continue;
+ }
- if (this.tsUtils.needToDeduceStructuralIdentity(tsParamType, tsArgType, tsArg)) {
+ if (
+ this.tsUtils.needToDeduceStructuralIdentity(
+ tsParamType,
+ tsArgType,
+ tsArg
+ )
+ ) {
this.incrementCounters(tsArg, FaultID.StructuralIdentity);
}
}
@@ -1816,161 +2127,192 @@ export class TypeScriptLinter {
private checkLimitedStdLib(node: ts.Node, symbol: ts.Symbol): void {
const parName = this.tsUtils.getParentSymbolName(symbol);
- const res = parName ? TsUtils.LIMITED_STD_API.get(parName) : undefined;
- if (res && res.arr.includes(symbol.name)) {
- this.incrementCounters(node, res.fault);
+ const entries = LIMITED_STD_API.get(parName);
+ if (!entries) {
return;
}
- const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
- if (TsUtils.LIMITED_STD_GLOBAL_API.includes(name)) {
- this.incrementCounters(node, FaultID.LimitedStdLibApi);
- return;
+ for (const entry of entries) {
+ if (entry.api.includes(symbol.name)) {
+ this.incrementCounters(node, entry.faultId);
+ return;
+ }
}
}
- private findNonFilteringRangesFunctionCalls(callExpr: ts.CallExpression): { begin: number, end: number }[] {
- let args = callExpr.arguments;
- let result: { begin: number, end: number }[] = [];
+ private static findNonFilteringRangesFunctionCalls(
+ callExpr: ts.CallExpression
+ ): { begin: number; end: number }[] {
+ const args = callExpr.arguments;
+ const result: { begin: number; end: number }[] = [];
for (const arg of args) {
if (ts.isArrowFunction(arg)) {
- let arrowFuncExpr = arg as ts.ArrowFunction;
- result.push({begin: arrowFuncExpr.body.pos, end: arrowFuncExpr.body.end});
+ const arrowFuncExpr = arg;
+ result.push({
+ begin: arrowFuncExpr.body.pos,
+ end: arrowFuncExpr.body.end,
+ });
} else if (ts.isCallExpression(arg)) {
- result.push({begin: arg.arguments.pos, end: arg.arguments.end});
+ result.push({ begin: arg.arguments.pos, end: arg.arguments.end });
}
// there may be other cases
}
return result;
}
- private handleLibraryTypeCall(callExpr: ts.CallExpression, calleeType: ts.Type): void {
- let inLibCall = this.tsUtils.isLibraryType(calleeType);
+ private handleLibraryTypeCall(callExpr: ts.CallExpression): void {
+ const calleeType = this.tsTypeChecker.getTypeAtLocation(
+ callExpr.expression
+ );
+ const inLibCall = this.tsUtils.isLibraryType(calleeType);
const diagnosticMessages: Array = [];
- this.libraryTypeCallDiagnosticChecker.configure(inLibCall, diagnosticMessages);
+ this.libraryTypeCallDiagnosticChecker.configure(
+ inLibCall,
+ diagnosticMessages
+ );
- let nonFilteringRanges = this.findNonFilteringRangesFunctionCalls(callExpr);
- let rangesToFilter: { begin: number, end: number }[] = [];
+ const nonFilteringRanges =
+ TypeScriptLinter.findNonFilteringRangesFunctionCalls(callExpr);
+ const rangesToFilter: { begin: number; end: number }[] = [];
if (nonFilteringRanges.length !== 0) {
- let rangesSize = nonFilteringRanges.length;
- rangesToFilter.push({ begin: callExpr.pos, end: nonFilteringRanges[0].begin });
- rangesToFilter.push({ begin: nonFilteringRanges[rangesSize - 1].end, end: callExpr.end });
+ const rangesSize = nonFilteringRanges.length;
+ rangesToFilter.push({
+ begin: callExpr.arguments.pos,
+ end: nonFilteringRanges[0].begin,
+ });
+ rangesToFilter.push({
+ begin: nonFilteringRanges[rangesSize - 1].end,
+ end: callExpr.arguments.end,
+ });
for (let i = 0; i < rangesSize - 1; i++) {
- rangesToFilter.push({ begin: nonFilteringRanges[i].end, end: nonFilteringRanges[i + 1].begin });
+ rangesToFilter.push({
+ begin: nonFilteringRanges[i].end,
+ end: nonFilteringRanges[i + 1].begin,
+ });
}
} else {
- rangesToFilter.push({ begin: callExpr.pos, end: callExpr.end });
- }
-
- this.filterStrictDiagnostics({
- [ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE]: (pos: number) => {
- return this.checkInRange(rangesToFilter, pos);
- },
+ rangesToFilter.push({
+ begin: callExpr.arguments.pos,
+ end: callExpr.arguments.end,
+ });
+ }
+
+ this.filterStrictDiagnostics(
+ {
+ [ARGUMENT_OF_TYPE_0_IS_NOT_ASSIGNABLE_TO_PARAMETER_OF_TYPE_1_ERROR_CODE]:
+ (pos: number) => {
+ return TypeScriptLinter.checkInRange(rangesToFilter, pos);
+ },
[TYPE_0_IS_NOT_ASSIGNABLE_TO_TYPE_1_ERROR_CODE]: (pos: number) => {
- return this.checkInRange(rangesToFilter, pos);
- }
+ return TypeScriptLinter.checkInRange(rangesToFilter, pos);
+ },
},
this.libraryTypeCallDiagnosticChecker
);
for (const msgChain of diagnosticMessages) {
- TypeScriptLinter.filteredDiagnosticMessages.add(msgChain)
+ TypeScriptLinter.filteredDiagnosticMessages.add(msgChain);
}
}
- private handleNewExpression(node: ts.Node) {
- let tsNewExpr = node as ts.NewExpression;
- let callSignature = this.tsTypeChecker.getResolvedSignature(tsNewExpr);
+ private handleNewExpression(node: ts.Node): void {
+ const tsNewExpr = node as ts.NewExpression;
+ const callSignature = this.tsTypeChecker.getResolvedSignature(tsNewExpr);
if (callSignature !== undefined) {
this.handleStructIdentAndUndefinedInArgs(tsNewExpr, callSignature);
this.handleGenericCallWithNoTypeArgs(tsNewExpr, callSignature);
}
}
- private handleAsExpression(node: ts.Node) {
- let tsAsExpr = node as ts.AsExpression;
- if (tsAsExpr.type.getText() === "const")
+ private handleAsExpression(node: ts.Node): void {
+ const tsAsExpr = node as ts.AsExpression;
+ if (tsAsExpr.type.getText() === 'const') {
this.incrementCounters(node, FaultID.ConstAssertion);
+ }
- let targetType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.type).getNonNullableType();
- let exprType = this.tsTypeChecker.getTypeAtLocation(tsAsExpr.expression).getNonNullableType();
- if (this.tsUtils.needToDeduceStructuralIdentity(targetType, exprType, tsAsExpr.expression, true)) {
+ const targetType = this.tsTypeChecker
+ .getTypeAtLocation(tsAsExpr.type)
+ .getNonNullableType();
+ const exprType = this.tsTypeChecker
+ .getTypeAtLocation(tsAsExpr.expression)
+ .getNonNullableType();
+ if (
+ this.tsUtils.needToDeduceStructuralIdentity(
+ targetType,
+ exprType,
+ tsAsExpr.expression,
+ true
+ )
+ ) {
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")
- )
+ (TsUtils.isNumberType(exprType) &&
+ targetType.getSymbol()?.getName() === 'Number') ||
+ (TsUtils.isBooleanType(exprType) &&
+ targetType.getSymbol()?.getName() === 'Boolean')
+ ) {
this.incrementCounters(node, FaultID.TypeAssertion);
+ }
}
- private handleTypeReference(node: ts.Node) {
+ private handleTypeReference(node: ts.Node): void {
const typeRef = node as ts.TypeReferenceNode;
- const isESObject = this.tsUtils.isEsObjectType(typeRef);
- const isPossiblyValidContext = this.tsUtils.isEsObjectPossiblyAllowed(typeRef);
+ const isESObject = TsUtils.isEsObjectType(typeRef);
+ const isPossiblyValidContext = TsUtils.isEsObjectPossiblyAllowed(typeRef);
if (isESObject && !isPossiblyValidContext) {
this.incrementCounters(node, FaultID.EsObjectType);
return;
}
-
const typeName = this.tsUtils.entityNameToString(typeRef.typeName);
- const isStdUtilityType = TsUtils.LIMITED_STANDARD_UTILITY_TYPES.includes(typeName);
+ const isStdUtilityType =
+ TsUtils.LIMITED_STANDARD_UTILITY_TYPES.includes(typeName);
if (isStdUtilityType) {
this.incrementCounters(node, FaultID.UtilityType);
return;
}
// Using Partial type is allowed only when its argument type is either Class or Interface.
- const isStdPartial = this.tsUtils.entityNameToString(typeRef.typeName) === 'Partial';
- const hasSingleTypeArgument = !!typeRef.typeArguments && typeRef.typeArguments.length === 1;
- const firstTypeArg = !!typeRef.typeArguments && hasSingleTypeArgument && typeRef.typeArguments[0];
- const argType = firstTypeArg && this.tsTypeChecker.getTypeFromTypeNode(firstTypeArg);
+ const isStdPartial =
+ this.tsUtils.entityNameToString(typeRef.typeName) === 'Partial';
+ const hasSingleTypeArgument =
+ !!typeRef.typeArguments && typeRef.typeArguments.length === 1;
+ const firstTypeArg =
+ !!typeRef.typeArguments &&
+ hasSingleTypeArgument &&
+ typeRef.typeArguments[0];
+ const argType =
+ firstTypeArg && this.tsTypeChecker.getTypeFromTypeNode(firstTypeArg);
if (isStdPartial && argType && !argType.isClassOrInterface()) {
this.incrementCounters(node, FaultID.UtilityType);
- return;
}
}
- private handleMetaProperty(node: ts.Node) {
- let tsMetaProperty = node as ts.MetaProperty;
+ private handleMetaProperty(node: ts.Node): void {
+ const tsMetaProperty = node as ts.MetaProperty;
if (tsMetaProperty.name.text === 'target') {
this.incrementCounters(node, FaultID.NewTarget);
}
}
- private handleStructDeclaration(node: ts.Node) {
- node.forEachChild((child) => {
- // Skip synthetic constructor in Struct declaration.
- if (!ts.isConstructorDeclaration(child)) this.visitTSNode(child);
- });
- }
-
- private handleSpreadOp(node: ts.Node) {
- // spread assignment is disabled
- // spread element is allowed only for arrays as rest parameter
+ private handleSpreadOp(node: ts.Node): void {
+ /*
+ * spread assignment is disabled
+ * spread element is allowed only for arrays as rest parameter
+ */
if (ts.isSpreadElement(node)) {
- let spreadElemNode = node as ts.SpreadElement;
- let spreadExprType = this.tsTypeChecker.getTypeAtLocation(
+ const spreadElemNode = node;
+ const spreadExprType = this.tsTypeChecker.getTypeAtLocation(
spreadElemNode.expression
);
if (spreadExprType) {
- const spreadExprTypeNode = this.tsTypeChecker.typeToTypeNode(
- spreadExprType,
- undefined,
- ts.NodeBuilderFlags.None
- );
if (
- spreadExprTypeNode !== undefined &&
- (ts.isCallLikeExpression(node.parent) ||
- ts.isArrayLiteralExpression(node.parent))
+ ts.isCallLikeExpression(node.parent) ||
+ ts.isArrayLiteralExpression(node.parent)
) {
if (
- ts.isArrayTypeNode(spreadExprTypeNode) ||
- this.tsUtils.isTypedArray(spreadExprTypeNode) ||
- this.tsUtils.isDerivedFrom(spreadExprType, CheckType.Array)
+ this.tsUtils.isOrDerivedFrom(spreadExprType, this.tsUtils.isArray)
) {
return;
}
@@ -1980,7 +2322,7 @@ export class TypeScriptLinter {
this.incrementCounters(node, FaultID.SpreadOperator);
}
- private handleConstructSignature(node: ts.Node) {
+ private handleConstructSignature(node: ts.Node): void {
switch (node.parent.kind) {
case ts.SyntaxKind.TypeLiteral:
this.incrementCounters(node, FaultID.ConstructorType);
@@ -1989,93 +2331,143 @@ export class TypeScriptLinter {
this.incrementCounters(node, FaultID.ConstructorIface);
break;
default:
- return;
}
}
- 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).
+ private handleComments(node: ts.Node): void {
+ /*
+ * 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();
const parent = node.parent;
if (!parent || parent.getFullStart() !== node.getFullStart()) {
- let leadingComments = ts.getLeadingCommentRanges(
+ const leadingComments = ts.getLeadingCommentRanges(
srcText,
node.getFullStart()
);
if (leadingComments) {
for (const comment of leadingComments) {
- this.checkErrorSuppressingAnnotation(comment, srcText);
+ /*
+ * In the real-time linter comment from the first line is double proccessed.
+ * It may be caused by tsc, but it should be investigated. This is a workaround
+ */
+ if (
+ !this.walkedComments.has(comment.pos) &&
+ comment.pos !== comment.end
+ ) {
+ this.walkedComments.add(comment.pos);
+ this.checkErrorSuppressingAnnotation(comment, srcText);
+ }
}
}
}
if (!parent || parent.getEnd() !== node.getEnd()) {
- let trailingComments = ts.getTrailingCommentRanges(
+ const trailingComments = ts.getTrailingCommentRanges(
srcText,
node.getEnd()
);
if (trailingComments) {
for (const comment of trailingComments) {
- this.checkErrorSuppressingAnnotation(comment, srcText);
+ /*
+ * In the real-time linter comment from the first line is double proccessed.
+ * It may be caused by tsc, but it should be investigated. This is a workaround
+ */
+ if (
+ !this.walkedComments.has(comment.pos) &&
+ comment.pos !== comment.end
+ ) {
+ this.walkedComments.add(comment.pos);
+ this.checkErrorSuppressingAnnotation(comment, srcText);
+ }
}
}
}
}
- private handleExpressionWithTypeArguments(node: ts.Node) {
- let tsTypeExpr = node as ts.ExpressionWithTypeArguments;
- let symbol = this.tsUtils.trueSymbolAtLocation(tsTypeExpr.expression);
- if (!!symbol && this.tsUtils.isEsObjectSymbol(symbol)) {
+ private handleExpressionWithTypeArguments(node: ts.Node): void {
+ const tsTypeExpr = node as ts.ExpressionWithTypeArguments;
+ const symbol = this.tsUtils.trueSymbolAtLocation(tsTypeExpr.expression);
+ if (!!symbol && TsUtils.isEsObjectSymbol(symbol)) {
this.incrementCounters(tsTypeExpr, FaultID.EsObjectType);
}
}
+ private handleComputedPropertyName(node: ts.Node): void {
+ const computedProperty = node as ts.ComputedPropertyName;
+ const symbol = this.tsUtils.trueSymbolAtLocation(
+ computedProperty.expression
+ );
+ if (!!symbol && this.tsUtils.isSymbolIterator(symbol)) {
+ return;
+ }
+ const isEnumMember =
+ !!symbol && !!(symbol.flags & ts.SymbolFlags.EnumMember);
+ const type = this.tsTypeChecker.getTypeAtLocation(
+ computedProperty.expression
+ );
+ const isStringEnumLiteral =
+ TsUtils.isEnumType(type) && !!(type.flags & ts.TypeFlags.StringLiteral);
+ if (isEnumMember && isStringEnumLiteral) {
+ return;
+ }
+ this.incrementCounters(node, FaultID.ComputedPropertyName);
+ }
+
private checkErrorSuppressingAnnotation(
comment: ts.CommentRange,
srcText: string
- ) {
+ ): void {
const commentContent =
comment.kind === ts.SyntaxKind.MultiLineCommentTrivia
? srcText.slice(comment.pos + 2, comment.end - 2)
: srcText.slice(comment.pos + 2, comment.end);
-
- let trimmedContent = commentContent.trim();
+ // if comment is multiline end closing '*/' is not at the same line as '@ts-xxx' - do nothing (see #13851)
+ if (commentContent.endsWith('\n')) {
+ return;
+ }
+ const trimmedContent = commentContent.trim();
if (
- trimmedContent.startsWith("@ts-ignore") ||
- trimmedContent.startsWith("@ts-nocheck") ||
- trimmedContent.startsWith("@ts-expect-error")
- )
+ trimmedContent.startsWith('@ts-ignore') ||
+ trimmedContent.startsWith('@ts-nocheck') ||
+ trimmedContent.startsWith('@ts-expect-error')
+ ) {
this.incrementCounters(comment, FaultID.ErrorSuppression);
+ }
}
private handleDecorators(
decorators: readonly ts.Decorator[] | undefined
): void {
- if (!decorators) return;
+ if (!decorators) {
+ return;
+ }
for (const decorator of decorators) {
- let decoratorName = "";
- if (ts.isIdentifier(decorator.expression))
+ let decoratorName = '';
+ if (ts.isIdentifier(decorator.expression)) {
decoratorName = decorator.expression.text;
- else if (
+ } else if (
ts.isCallExpression(decorator.expression) &&
ts.isIdentifier(decorator.expression.expression)
- )
+ ) {
decoratorName = decorator.expression.expression.text;
+ }
- if (!TsUtils.ARKUI_DECORATORS.includes(decoratorName))
+ if (!TsUtils.ARKUI_DECORATORS.includes(decoratorName)) {
this.incrementCounters(decorator, FaultID.UnsupportedDecorators);
+ }
}
}
- private handleGetAccessor(node: ts.Node) {
+ private handleGetAccessor(node: ts.Node): void {
this.handleDecorators((node as ts.GetAccessorDeclaration).decorators);
}
- private handleSetAccessor(node: ts.Node) {
+ private handleSetAccessor(node: ts.Node): void {
this.handleDecorators((node as ts.SetAccessorDeclaration).decorators);
}
@@ -2084,55 +2476,72 @@ export class TypeScriptLinter {
| ts.VariableDeclaration
| ts.PropertyDeclaration
| ts.ParameterDeclaration
- ) {
+ ): void {
// The type is explicitly specified, no need to check inferred type.
- if (decl.type) return;
+ if (decl.type) {
+ return;
+ }
- // issue 13161:
- // In TypeScript, the catch clause variable must be 'any' or 'unknown' type. Since
- // ArkTS doesn't support these types, the type for such variable is simply omitted,
- // and we don't report it as an error.
- if (ts.isCatchClause(decl.parent)) return;
+ /*
+ * issue 13161:
+ * In TypeScript, the catch clause variable must be 'any' or 'unknown' type. Since
+ * ArkTS doesn't support these types, the type for such variable is simply omitted,
+ * and we don't report it as an error.
+ */
+ if (ts.isCatchClause(decl.parent)) {
+ return;
+ }
// Destructuring declarations are not supported, do not process them.
if (
ts.isArrayBindingPattern(decl.name) ||
ts.isObjectBindingPattern(decl.name)
- )
+ ) {
return;
+ }
- // issue 13987:
- // When variable have no type annotation and no initial value, and 'noImplicitAny'
- // option is enabled, compiler attempts to infer type from variable references:
- // see https://github.com/microsoft/TypeScript/pull/11263.
- // In this case, we still want to report the error, since ArkTS doesn't allow
- // to omit both type annotation and initializer.
- if (((ts.isVariableDeclaration(decl) && ts.isVariableStatement(decl.parent.parent)) || ts.isPropertyDeclaration(decl)) &&
- !decl.initializer) {
+ /*
+ * issue 13987:
+ * When variable have no type annotation and no initial value, and 'noImplicitAny'
+ * option is enabled, compiler attempts to infer type from variable references:
+ * see https://github.com/microsoft/TypeScript/pull/11263.
+ * In this case, we still want to report the error, since ArkTS doesn't allow
+ * to omit both type annotation and initializer.
+ */
+ if (
+ ((ts.isVariableDeclaration(decl) &&
+ ts.isVariableStatement(decl.parent.parent)) ||
+ ts.isPropertyDeclaration(decl)) &&
+ !decl.initializer
+ ) {
this.incrementCounters(decl, FaultID.AnyType);
return;
}
const type = this.tsTypeChecker.getTypeAtLocation(decl);
- if (type) this.validateDeclInferredType(type, decl);
+ if (type) {
+ this.validateDeclInferredType(type, decl);
+ }
}
private handleDefiniteAssignmentAssertion(
decl: ts.VariableDeclaration | ts.PropertyDeclaration
- ) {
+ ): void {
if (decl.exclamationToken !== undefined) {
this.incrementCounters(decl, FaultID.DefiniteAssignment);
}
}
- private validatedTypesSet = new Set();
+ private readonly validatedTypesSet = new Set();
private checkAnyOrUnknownChildNode(node: ts.Node): boolean {
- if (node.kind === ts.SyntaxKind.AnyKeyword ||
- node.kind === ts.SyntaxKind.UnknownKeyword) {
+ if (
+ node.kind === ts.SyntaxKind.AnyKeyword ||
+ node.kind === ts.SyntaxKind.UnknownKeyword
+ ) {
return true;
}
- for (let child of node.getChildren()) {
+ for (const child of node.getChildren()) {
if (this.checkAnyOrUnknownChildNode(child)) {
return true;
}
@@ -2142,9 +2551,14 @@ export class TypeScriptLinter {
private handleInferredObjectreference(
type: ts.Type,
- decl: ts.VariableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration
- ) {
- const typeArgs = this.tsTypeChecker.getTypeArguments(type as ts.TypeReference);
+ decl:
+ | ts.VariableDeclaration
+ | ts.PropertyDeclaration
+ | ts.ParameterDeclaration
+ ): void {
+ const typeArgs = this.tsTypeChecker.getTypeArguments(
+ type as ts.TypeReference
+ );
if (typeArgs) {
const haveAnyOrUnknownNodes = this.checkAnyOrUnknownChildNode(decl);
if (!haveAnyOrUnknownNodes) {
@@ -2165,9 +2579,10 @@ export class TypeScriptLinter {
if (type.aliasSymbol !== undefined) {
return;
}
- const isObject = type.flags & ts.TypeFlags.Object;
- const isReference = (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference;
- if (isObject && isReference) {
+ if (
+ TsUtils.isObjectType(type) &&
+ !!(type.objectFlags & ts.ObjectFlags.Reference)
+ ) {
this.handleInferredObjectreference(type, decl);
return;
}
@@ -2176,19 +2591,120 @@ export class TypeScriptLinter {
}
if (type.isUnion()) {
this.validatedTypesSet.add(type);
- for (let unionElem of type.types) {
- this.validateDeclInferredType(unionElem, decl)
+ for (const unionElem of type.types) {
+ this.validateDeclInferredType(unionElem, decl);
}
}
- if (this.tsUtils.isAnyType(type))
+ if (TsUtils.isAnyType(type)) {
this.incrementCounters(decl, FaultID.AnyType);
- else if (this.tsUtils.isUnknownType(type))
+ } else if (TsUtils.isUnknownType(type)) {
this.incrementCounters(decl, FaultID.UnknownType);
+ }
+ }
+
+ private handleCommentDirectives(sourceFile: ts.SourceFile): void {
+ /*
+ * We use a dirty hack to retrieve list of parsed comment directives by accessing
+ * internal properties of SourceFile node.
+ */
+
+ // Handle comment directive '@ts-nocheck'
+ const pragmas = (sourceFile as any).pragmas;
+ if (pragmas && pragmas instanceof Map) {
+ const noCheckPragma = pragmas.get('ts-nocheck');
+ if (noCheckPragma) {
+ /*
+ * The value is either a single entry or an array of entries.
+ * Wrap up single entry with array to simplify processing.
+ */
+ const noCheckEntries: any[] = Array.isArray(noCheckPragma) ?
+ noCheckPragma :
+ [noCheckPragma];
+ for (const entry of noCheckEntries) {
+ this.processNoCheckEntry(entry);
+ }
+ }
+ }
+
+ // Handle comment directives '@ts-ignore' and '@ts-expect-error'
+ const commentDirectives = (sourceFile as any).commentDirectives;
+ if (commentDirectives && Array.isArray(commentDirectives)) {
+ for (const directive of commentDirectives) {
+ if (
+ directive.range?.pos === undefined ||
+ directive.range?.end === undefined
+ ) {
+ continue;
+ }
+
+ const range = directive.range as ts.TextRange;
+ const commentOpen = '/*';
+ const kind: ts.SyntaxKind =
+ sourceFile.text.slice(range.pos, range.pos + commentOpen.length) === commentOpen ?
+ ts.SyntaxKind.MultiLineCommentTrivia :
+ ts.SyntaxKind.SingleLineCommentTrivia;
+ const commentRange: ts.CommentRange = {
+ pos: range.pos,
+ end: range.end,
+ kind,
+ };
+
+ this.incrementCounters(commentRange, FaultID.ErrorSuppression);
+ }
+ }
+ }
+
+ private processNoCheckEntry(entry: any): void {
+ if (
+ entry.range?.kind === undefined ||
+ entry.range?.pos === undefined ||
+ entry.range?.end === undefined
+ ) {
+ return;
+ }
+
+ this.incrementCounters(
+ entry.range as ts.CommentRange,
+ FaultID.ErrorSuppression
+ );
+ }
+
+ private reportThisKeywordsInScope(scope: ts.Block | ts.Expression): void {
+ const callback = (node: ts.Node): void => {
+ if (node.kind === ts.SyntaxKind.ThisKeyword) {
+ this.incrementCounters(node, FaultID.FunctionContainsThis);
+ }
+ };
+ const stopCondition = (node: ts.Node): boolean => {
+ const isClassLike =
+ ts.isClassDeclaration(node) || ts.isClassExpression(node);
+ const isFunctionLike =
+ ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node);
+ const isModuleDecl = ts.isModuleDeclaration(node);
+ return isClassLike || isFunctionLike || isModuleDecl;
+ };
+ this.forEachNodeInSubtree(scope, callback, stopCondition);
+ }
+
+ private static hasPredecessor(
+ node: ts.Node,
+ predicate: (node: ts.Node) => boolean
+ ): boolean {
+ let parent = node.parent;
+ while (parent !== undefined) {
+ if (predicate(parent)) {
+ return true;
+ }
+ parent = parent.parent;
+ }
+ return false;
}
- public lint(sourceFile: ts.SourceFile) {
+ lint(sourceFile: ts.SourceFile): void {
+ this.walkedComments.clear();
this.sourceFile = sourceFile;
- this.visitTSNode(this.sourceFile);
+ this.visitSourceFile(this.sourceFile);
+ this.handleCommentDirectives(this.sourceFile);
}
}
diff --git a/ets2panda/linter-4.2/src/TypeScriptLinterConfig.ts b/ets2panda/linter-4.2/src/TypeScriptLinterConfig.ts
index 1639e5664f9758c32dc413580caf9e94ec5956d9..44d9a44d19a060f6dd087c3be6c86a970ff5b3fd 100644
--- a/ets2panda/linter-4.2/src/TypeScriptLinterConfig.ts
+++ b/ets2panda/linter-4.2/src/TypeScriptLinterConfig.ts
@@ -19,27 +19,29 @@ 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.
+ /*
+ * 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() {
+ static initStatic(): void {
// Set the feature descriptions (for the output).
LinterConfig.nodeDesc[FaultID.AnyType] = '"any" type';
LinterConfig.nodeDesc[FaultID.SymbolType] = '"symbol" type';
- LinterConfig.nodeDesc[FaultID.ObjectLiteralNoContextType] = 'Object literals with no context Class or Interface type';
+ LinterConfig.nodeDesc[FaultID.ObjectLiteralNoContextType] =
+ 'Object literals with no context Class or Interface type';
LinterConfig.nodeDesc[FaultID.ArrayLiteralNoContextType] = 'Array literals with no context Array type';
LinterConfig.nodeDesc[FaultID.ComputedPropertyName] = 'Computed properties';
LinterConfig.nodeDesc[FaultID.LiteralAsPropertyName] = 'String or integer literal as property name';
LinterConfig.nodeDesc[FaultID.TypeQuery] = '"typeof" operations';
- LinterConfig.nodeDesc[FaultID.RegexLiteral] = 'regex literals';
LinterConfig.nodeDesc[FaultID.IsOperator] = '"is" operations';
LinterConfig.nodeDesc[FaultID.DestructuringParameter] = 'destructuring parameters';
LinterConfig.nodeDesc[FaultID.YieldExpression] = '"yield" operations';
@@ -92,8 +94,6 @@ export class LinterConfig {
LinterConfig.nodeDesc[FaultID.ThisType] = '"this" type';
LinterConfig.nodeDesc[FaultID.IntefaceExtendDifProps] = 'Extends same properties with different types';
LinterConfig.nodeDesc[FaultID.StructuralIdentity] = 'Use of type structural identity';
- LinterConfig.nodeDesc[FaultID.TypeOnlyImport] = 'Type-only imports';
- LinterConfig.nodeDesc[FaultID.TypeOnlyExport] = 'Type-only exports';
LinterConfig.nodeDesc[FaultID.DefaultImport] = 'Default import declarations';
LinterConfig.nodeDesc[FaultID.ExportAssignment] = 'Export assignments (export = ..)';
LinterConfig.nodeDesc[FaultID.ImportAssignment] = 'Import assignments (import = ..)';
@@ -109,7 +109,8 @@ export class LinterConfig {
LinterConfig.nodeDesc[FaultID.GlobalThis] = 'Use of globalThis';
LinterConfig.nodeDesc[FaultID.UtilityType] = 'Standard Utility types';
LinterConfig.nodeDesc[FaultID.PropertyDeclOnFunction] = 'Property declaration on function';
- LinterConfig.nodeDesc[FaultID.FunctionApplyBindCall] = 'Invoking methods of function objects';
+ LinterConfig.nodeDesc[FaultID.FunctionApplyCall] = 'Invoking methods of function objects';
+ LinterConfig.nodeDesc[FaultID.FunctionBind] = 'Invoking methods of function objects';
LinterConfig.nodeDesc[FaultID.ConstAssertion] = '"as const" assertion';
LinterConfig.nodeDesc[FaultID.ImportAssertion] = 'Import assertion';
LinterConfig.nodeDesc[FaultID.SpreadOperator] = 'Spread operation';
@@ -119,7 +120,6 @@ export class LinterConfig {
LinterConfig.nodeDesc[FaultID.UnsupportedDecorators] = 'Unsupported decorators';
LinterConfig.nodeDesc[FaultID.ImportAfterStatement] = 'Import declaration after other declaration or statement';
LinterConfig.nodeDesc[FaultID.EsObjectType] = 'Restricted "ESObject" type';
-
LinterConfig.initTsSyntaxKindNames();
}
@@ -138,48 +138,96 @@ export class LinterConfig {
// must detect terminals during parsing
static terminalTokens: Set = new Set([
- ts.SyntaxKind.OpenBraceToken, ts.SyntaxKind.CloseBraceToken, ts.SyntaxKind.OpenParenToken,
- ts.SyntaxKind.CloseParenToken, ts.SyntaxKind.OpenBracketToken, ts.SyntaxKind.CloseBracketToken,
- ts.SyntaxKind.DotToken, ts.SyntaxKind.DotDotDotToken, ts.SyntaxKind.SemicolonToken, ts.SyntaxKind.CommaToken,
- ts.SyntaxKind.QuestionDotToken, ts.SyntaxKind.LessThanToken, ts.SyntaxKind.LessThanSlashToken,
- ts.SyntaxKind.GreaterThanToken, ts.SyntaxKind.LessThanEqualsToken, ts.SyntaxKind.GreaterThanEqualsToken,
- ts.SyntaxKind.EqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsToken, ts.SyntaxKind.EqualsEqualsEqualsToken,
- ts.SyntaxKind.ExclamationEqualsEqualsToken, ts.SyntaxKind.EqualsGreaterThanToken, ts.SyntaxKind.PlusToken,
- ts.SyntaxKind.MinusToken, ts.SyntaxKind.AsteriskToken, ts.SyntaxKind.AsteriskAsteriskToken,
- ts.SyntaxKind.SlashToken, ts.SyntaxKind.PercentToken, ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken,
- ts.SyntaxKind.LessThanLessThanToken, ts.SyntaxKind.GreaterThanGreaterThanToken,
- ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, ts.SyntaxKind.AmpersandToken, ts.SyntaxKind.BarToken,
- ts.SyntaxKind.CaretToken, ts.SyntaxKind.ExclamationToken, ts.SyntaxKind.TildeToken,
- ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken,
- ts.SyntaxKind.QuestionToken, ts.SyntaxKind.ColonToken, ts.SyntaxKind.AtToken, ts.SyntaxKind.BacktickToken,
- ts.SyntaxKind.EqualsToken, ts.SyntaxKind.PlusEqualsToken, ts.SyntaxKind.MinusEqualsToken,
- ts.SyntaxKind.AsteriskEqualsToken, ts.SyntaxKind.AsteriskAsteriskEqualsToken, ts.SyntaxKind.SlashEqualsToken,
- ts.SyntaxKind.PercentEqualsToken, ts.SyntaxKind.LessThanLessThanEqualsToken,
- ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
- ts.SyntaxKind.AmpersandEqualsToken, ts.SyntaxKind.BarEqualsToken, ts.SyntaxKind.CaretEqualsToken,
- ts.SyntaxKind.EndOfFileToken, ts.SyntaxKind.SingleLineCommentTrivia,
- ts.SyntaxKind.MultiLineCommentTrivia, ts.SyntaxKind.NewLineTrivia, ts.SyntaxKind.WhitespaceTrivia,
- ts.SyntaxKind.ShebangTrivia, /* We detect and preserve #! on the first line */ ts.SyntaxKind.ConflictMarkerTrivia,
+ ts.SyntaxKind.OpenBraceToken,
+ ts.SyntaxKind.CloseBraceToken,
+ ts.SyntaxKind.OpenParenToken,
+ ts.SyntaxKind.CloseParenToken,
+ ts.SyntaxKind.OpenBracketToken,
+ ts.SyntaxKind.CloseBracketToken,
+ ts.SyntaxKind.DotToken,
+ ts.SyntaxKind.DotDotDotToken,
+ ts.SyntaxKind.SemicolonToken,
+ ts.SyntaxKind.CommaToken,
+ ts.SyntaxKind.QuestionDotToken,
+ ts.SyntaxKind.LessThanToken,
+ ts.SyntaxKind.LessThanSlashToken,
+ ts.SyntaxKind.GreaterThanToken,
+ ts.SyntaxKind.LessThanEqualsToken,
+ ts.SyntaxKind.GreaterThanEqualsToken,
+ ts.SyntaxKind.EqualsEqualsToken,
+ ts.SyntaxKind.ExclamationEqualsToken,
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
+ ts.SyntaxKind.EqualsGreaterThanToken,
+ ts.SyntaxKind.PlusToken,
+ ts.SyntaxKind.MinusToken,
+ ts.SyntaxKind.AsteriskToken,
+ ts.SyntaxKind.AsteriskAsteriskToken,
+ ts.SyntaxKind.SlashToken,
+ ts.SyntaxKind.PercentToken,
+ ts.SyntaxKind.PlusPlusToken,
+ ts.SyntaxKind.MinusMinusToken,
+ ts.SyntaxKind.LessThanLessThanToken,
+ ts.SyntaxKind.GreaterThanGreaterThanToken,
+ ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken,
+ ts.SyntaxKind.AmpersandToken,
+ ts.SyntaxKind.BarToken,
+ ts.SyntaxKind.CaretToken,
+ ts.SyntaxKind.ExclamationToken,
+ ts.SyntaxKind.TildeToken,
+ ts.SyntaxKind.AmpersandAmpersandToken,
+ ts.SyntaxKind.BarBarToken,
+ ts.SyntaxKind.QuestionQuestionToken,
+ ts.SyntaxKind.QuestionToken,
+ ts.SyntaxKind.ColonToken,
+ ts.SyntaxKind.AtToken,
+ ts.SyntaxKind.BacktickToken,
+ ts.SyntaxKind.EqualsToken,
+ ts.SyntaxKind.PlusEqualsToken,
+ ts.SyntaxKind.MinusEqualsToken,
+ ts.SyntaxKind.AsteriskEqualsToken,
+ ts.SyntaxKind.AsteriskAsteriskEqualsToken,
+ ts.SyntaxKind.SlashEqualsToken,
+ ts.SyntaxKind.PercentEqualsToken,
+ ts.SyntaxKind.LessThanLessThanEqualsToken,
+ ts.SyntaxKind.GreaterThanGreaterThanEqualsToken,
+ ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken,
+ ts.SyntaxKind.AmpersandEqualsToken,
+ ts.SyntaxKind.BarEqualsToken,
+ ts.SyntaxKind.CaretEqualsToken,
+ ts.SyntaxKind.EndOfFileToken,
+ ts.SyntaxKind.SingleLineCommentTrivia,
+ ts.SyntaxKind.MultiLineCommentTrivia,
+ ts.SyntaxKind.NewLineTrivia,
+ ts.SyntaxKind.WhitespaceTrivia,
+ // We detect and preserve #! on the first line
+ ts.SyntaxKind.ShebangTrivia,
+ ts.SyntaxKind.ConflictMarkerTrivia
]);
// tokens which can be reported without additional parsing
static incrementOnlyTokens: Map = new Map([
- [ts.SyntaxKind.AnyKeyword, FaultID.AnyType], [ts.SyntaxKind.SymbolKeyword, FaultID.SymbolType],
+ [ts.SyntaxKind.AnyKeyword, FaultID.AnyType],
+ [ts.SyntaxKind.SymbolKeyword, FaultID.SymbolType],
[ts.SyntaxKind.ThisType, FaultID.ThisType],
- [ts.SyntaxKind.ComputedPropertyName, FaultID.ComputedPropertyName],
[ts.SyntaxKind.TypeQuery, FaultID.TypeQuery],
[ts.SyntaxKind.DeleteExpression, FaultID.DeleteOperator],
- [ts.SyntaxKind.RegularExpressionLiteral, FaultID.RegexLiteral],
- [ts.SyntaxKind.TypePredicate, FaultID.IsOperator], [ts.SyntaxKind.YieldExpression, FaultID.YieldExpression],
- [ts.SyntaxKind.IndexSignature, FaultID.IndexMember], [ts.SyntaxKind.WithStatement, FaultID.WithStatement],
- [ts.SyntaxKind.IndexedAccessType, FaultID.IndexedAccessType],[ts.SyntaxKind.UnknownKeyword, FaultID.UnknownType],
- [ts.SyntaxKind.InKeyword, FaultID.InOperator], [ts.SyntaxKind.CallSignature, FaultID.CallSignature],
+ [ts.SyntaxKind.TypePredicate, FaultID.IsOperator],
+ [ts.SyntaxKind.YieldExpression, FaultID.YieldExpression],
+ [ts.SyntaxKind.IndexSignature, FaultID.IndexMember],
+ [ts.SyntaxKind.WithStatement, FaultID.WithStatement],
+ [ts.SyntaxKind.IndexedAccessType, FaultID.IndexedAccessType],
+ [ts.SyntaxKind.UnknownKeyword, FaultID.UnknownType],
+ [ts.SyntaxKind.CallSignature, FaultID.CallSignature],
[ts.SyntaxKind.IntersectionType, FaultID.IntersectionType],
- [ts.SyntaxKind.TypeLiteral, FaultID.ObjectTypeLiteral], [ts.SyntaxKind.ConstructorType, FaultID.ConstructorFuncs],
+ [ts.SyntaxKind.TypeLiteral, FaultID.ObjectTypeLiteral],
+ [ts.SyntaxKind.ConstructorType, FaultID.ConstructorFuncs],
[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.ConditionalType, FaultID.ConditionalType],
+ [ts.SyntaxKind.MappedType, FaultID.MappedType],
+ [ts.SyntaxKind.JsxElement, FaultID.JsxElement],
+ [ts.SyntaxKind.JsxSelfClosingElement, FaultID.JsxElement],
[ts.SyntaxKind.ImportEqualsDeclaration, FaultID.ImportAssignment],
- [ts.SyntaxKind.NamespaceExportDeclaration, FaultID.UMDModuleDefinition],
+ [ts.SyntaxKind.NamespaceExportDeclaration, FaultID.UMDModuleDefinition]
]);
}
diff --git a/ets2panda/linter-4.2/src/Utils.ts b/ets2panda/linter-4.2/src/Utils.ts
index fa383a3e68e71573ba75e0e221a1ab69aa9eaff3..46fe0d28b37aa0bd17127c4eeb7a885cdc94c38a 100644
--- a/ets2panda/linter-4.2/src/Utils.ts
+++ b/ets2panda/linter-4.2/src/Utils.ts
@@ -15,12 +15,15 @@
import * as path from 'node:path';
import * as ts from 'typescript';
-import { ProblemInfo } from './ProblemInfo';
-import { AutofixInfo } from './AutofixInfo';
-import { LinterConfig } from './TypeScriptLinterConfig'
-import { FaultID } from './Problems';
-
-export function logTscDiagnostic(diagnostics: readonly ts.Diagnostic[], log: (message: any, ...args: any[]) => void) {
+import type { ProblemInfo } from './ProblemInfo';
+import type { AutofixInfo } from './AutofixInfo';
+import { LinterConfig } from './TypeScriptLinterConfig';
+import { forEachNodeInSubtree } from './ForEachNodeInSubtree';
+
+export function logTscDiagnostic(
+ diagnostics: readonly ts.Diagnostic[],
+ log: (message: any, ...args: any[]) => void
+): void {
diagnostics.forEach((diagnostic) => {
let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
@@ -38,7 +41,7 @@ export function encodeProblemInfo(problem: ProblemInfo): string {
}
export function decodeAutofixInfo(info: string): AutofixInfo {
- let infos = info.split('%');
+ const infos = info.split('%');
return { problemID: infos[0], start: Number.parseInt(infos[1]), end: Number.parseInt(infos[2]) };
}
@@ -53,13 +56,11 @@ export function getNodeOrLineEnd(
sourceFile: ts.SourceFile,
nodeStartPos: number,
nodeEndPos: number,
- nodeStartLine: 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);
+ return pos.line + 1 === nodeStartLine ? pos.character : sourceFile.getLineEndOfPosition(nodeStartPos);
}
export function mergeArrayMaps(lhs: Map, rhs: Map): Map {
@@ -95,135 +96,11 @@ export function isAssignmentOperator(tsBinOp: ts.BinaryOperatorToken): boolean {
return tsBinOp.kind >= ts.SyntaxKind.FirstAssignment && tsBinOp.kind <= ts.SyntaxKind.LastAssignment;
}
-export enum CheckType {
- Array,
- String = "String",
- Set = "Set",
- Map = "Map",
- Error = "Error",
-};
+export type CheckType = (this: TsUtils, t: ts.Type) => boolean;
export class TsUtils {
static readonly ES_OBJECT = 'ESObject';
- private static readonly LIMITED_STD_ARRAYBUFFER_API = [
- // properties
- // methods
- 'isView'
- ];
-
- private static readonly LIMITED_STD_OBJECT_API = [
- // properties
- '__proto__',
- // methods
- '__defineGetter__',
- '__defineSetter__',
- '__lookupGetter__',
- '__lookupSetter__',
- 'assign',
- 'create',
- 'defineProperties',
- 'defineProperty',
- 'freeze',
- 'fromEntries',
- 'getOwnPropertyDescriptor',
- 'getOwnPropertyDescriptors',
- 'getOwnPropertySymbols',
- 'getPrototypeOf',
- 'hasOwnProperty',
- 'is',
- 'isExtensible',
- 'isFrozen',
- 'isPrototypeOf',
- 'isSealed',
- 'preventExtensions',
- 'propertyIsEnumerable',
- 'seal',
- 'setPrototypeOf',
- ];
-
- private static readonly LIMITED_STD_PROXYHANDLER_API = [
- // properties
- // methods
- 'apply',
- 'construct',
- 'defineProperty',
- 'deleteProperty',
- 'get',
- 'getOwnPropertyDescriptor',
- 'getPrototypeOf',
- 'has',
- 'isExtensible',
- 'ownKeys',
- 'preventExtensions',
- 'set',
- 'setPrototypeOf'
- ];
-
- private static readonly LIMITED_STD_REFLECT_API = [
- // properties
- // methods
- 'apply',
- 'construct',
- 'defineProperty',
- 'deleteProperty',
- 'getOwnPropertyDescriptor',
- 'getPrototypeOf',
- 'isExtensible',
- 'preventExtensions',
- 'setPrototypeOf',
- ];
-
- private static readonly LIMITED_STD_SYMBOL_API = [
- 'Symbol',
- // properties
- 'asyncIterator',
- 'description',
- 'hasInstance',
- 'isConcatSpreadable',
- 'match',
- 'matchAll',
- 'replace',
- 'search',
- 'species',
- 'split',
- 'toPrimitive',
- 'toStringTag',
- 'unscopables',
- // methods
- 'for',
- 'keyFor',
- 'toString',
- 'valueOf',
- ];
-
- private static readonly LIMITED_STD_FUNCTION_API = [
- // properties
- // methods
- 'apply',
- 'bind',
- 'call',
- ];
-
- static readonly LIMITED_STD_GLOBAL_API = [
- // properties
- // methods
- 'eval',
- ];
-
- static readonly LIMITED_STD_API = new Map, fault: FaultID}>([
- ['Object', {arr: TsUtils.LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}],
- ['ObjectConstructor', {arr: TsUtils.LIMITED_STD_OBJECT_API, fault: FaultID.LimitedStdLibApi}],
- ['Reflect', {arr: TsUtils.LIMITED_STD_REFLECT_API, fault: FaultID.LimitedStdLibApi}],
- ['ProxyHandler', {arr: TsUtils.LIMITED_STD_PROXYHANDLER_API, fault: FaultID.LimitedStdLibApi}],
- ['ArrayBuffer', {arr: TsUtils.LIMITED_STD_ARRAYBUFFER_API, fault: FaultID.LimitedStdLibApi}],
- ['ArrayBufferConstructor', {arr: TsUtils.LIMITED_STD_ARRAYBUFFER_API, fault: FaultID.LimitedStdLibApi}],
- ['Symbol', {arr: TsUtils.LIMITED_STD_SYMBOL_API, fault: FaultID.SymbolType}],
- ['SymbolConstructor', {arr: TsUtils.LIMITED_STD_SYMBOL_API, fault: FaultID.SymbolType}],
- ['Function', {arr: TsUtils.LIMITED_STD_FUNCTION_API, fault: FaultID.FunctionApplyBindCall}],
- ['CallableFunction', {arr: TsUtils.LIMITED_STD_FUNCTION_API, fault: FaultID.FunctionApplyBindCall}],
- ]);
-
static readonly NON_INITIALIZABLE_PROPERTY_DECORATORS = ['Link', 'Consume', 'ObjectLink', 'Prop', 'BuilderParam'];
static readonly NON_INITIALIZABLE_PROPERTY_CLASS_DECORATORS = ['CustomDialog'];
@@ -235,9 +112,23 @@ export class TsUtils {
static readonly FUNCTION_HAS_NO_RETURN_ERROR_CODE = 2366;
static readonly LIMITED_STANDARD_UTILITY_TYPES = [
- 'Awaited', 'Pick', 'Omit', 'Exclude', 'Extract', 'NonNullable', 'Parameters',
- 'ConstructorParameters', 'ReturnType', 'InstanceType', 'ThisParameterType', 'OmitThisParameter',
- 'ThisType', 'Uppercase', 'Lowercase', 'Capitalize', 'Uncapitalize',
+ 'Awaited',
+ 'Pick',
+ 'Omit',
+ 'Exclude',
+ 'Extract',
+ 'NonNullable',
+ 'Parameters',
+ 'ConstructorParameters',
+ 'ReturnType',
+ 'InstanceType',
+ 'ThisParameterType',
+ 'OmitThisParameter',
+ 'ThisType',
+ 'Uppercase',
+ 'Lowercase',
+ 'Capitalize',
+ 'Uncapitalize'
];
static readonly ARKUI_DECORATORS = [
@@ -263,24 +154,64 @@ export class TsUtils {
'StorageLink',
'StorageProp',
'Styles',
- 'Watch',
+ 'Watch'
];
static readonly STANDARD_LIBRARIES = [
- "lib.dom.d.ts", "lib.dom.iterable.d.ts", "lib.webworker.d.ts", "lib.webworker.importscripts.d.ts",
- "lib.webworker.iterable.d.ts", "lib.scripthost.d.ts", "lib.decorators.d.ts", "lib.decorators.legacy.d.ts",
- "lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.collection.d.ts", "lib.es2015.generator.d.ts",
- "lib.es2015.iterable.d.ts", "lib.es2015.promise.d.ts", "lib.es2015.proxy.d.ts", "lib.es2015.reflect.d.ts",
- "lib.es2015.symbol.d.ts", "lib.es2015.symbol.wellknown.d.ts", "lib.es2016.array.include.d.ts",
- "lib.es2017.object.d.ts", "lib.es2017.sharedmemory.d.ts", "lib.es2017.string.d.ts", "lib.es2017.intl.d.ts",
- "lib.es2017.typedarrays.d.ts", "lib.es2018.asyncgenerator.d.ts", "lib.es2018.asynciterable.d.ts",
- "lib.es2018.intl.d.ts", "lib.es2018.promise.d.ts", "lib.es2018.regexp.d.ts", "lib.es2019.array.d.ts",
- "lib.es2019.object.d.ts", "lib.es2019.string.d.ts", "lib.es2019.symbol.d.ts", "lib.es2019.intl.d.ts",
- "lib.es2020.bigint.d.ts", "lib.es2020.date.d.ts", "lib.es2020.promise.d.ts", "lib.es2020.sharedmemory.d.ts",
- "lib.es2020.string.d.ts", "lib.es2020.symbol.wellknown.d.ts", "lib.es2020.intl.d.ts", "lib.es2020.number.d.ts",
- "lib.es2021.promise.d.ts", "lib.es2021.string.d.ts", "lib.es2021.weakref.d.ts", "lib.es2021.intl.d.ts",
- "lib.es2022.array.d.ts", "lib.es2022.error.d.ts", "lib.es2022.intl.d.ts", "lib.es2022.object.d.ts",
- "lib.es2022.sharedmemory.d.ts", "lib.es2022.string.d.ts", "lib.es2022.regexp.d.ts", "lib.es2023.array.d.ts",
+ 'lib.dom.d.ts',
+ 'lib.dom.iterable.d.ts',
+ 'lib.webworker.d.ts',
+ 'lib.webworker.importscripts.d.ts',
+ 'lib.webworker.iterable.d.ts',
+ 'lib.scripthost.d.ts',
+ 'lib.decorators.d.ts',
+ 'lib.decorators.legacy.d.ts',
+ 'lib.es5.d.ts',
+ 'lib.es2015.core.d.ts',
+ 'lib.es2015.collection.d.ts',
+ 'lib.es2015.generator.d.ts',
+ 'lib.es2015.iterable.d.ts',
+ 'lib.es2015.promise.d.ts',
+ 'lib.es2015.proxy.d.ts',
+ 'lib.es2015.reflect.d.ts',
+ 'lib.es2015.symbol.d.ts',
+ 'lib.es2015.symbol.wellknown.d.ts',
+ 'lib.es2016.array.include.d.ts',
+ 'lib.es2017.object.d.ts',
+ 'lib.es2017.sharedmemory.d.ts',
+ 'lib.es2017.string.d.ts',
+ 'lib.es2017.intl.d.ts',
+ 'lib.es2017.typedarrays.d.ts',
+ 'lib.es2018.asyncgenerator.d.ts',
+ 'lib.es2018.asynciterable.d.ts',
+ 'lib.es2018.intl.d.ts',
+ 'lib.es2018.promise.d.ts',
+ 'lib.es2018.regexp.d.ts',
+ 'lib.es2019.array.d.ts',
+ 'lib.es2019.object.d.ts',
+ 'lib.es2019.string.d.ts',
+ 'lib.es2019.symbol.d.ts',
+ 'lib.es2019.intl.d.ts',
+ 'lib.es2020.bigint.d.ts',
+ 'lib.es2020.date.d.ts',
+ 'lib.es2020.promise.d.ts',
+ 'lib.es2020.sharedmemory.d.ts',
+ 'lib.es2020.string.d.ts',
+ 'lib.es2020.symbol.wellknown.d.ts',
+ 'lib.es2020.intl.d.ts',
+ 'lib.es2020.number.d.ts',
+ 'lib.es2021.promise.d.ts',
+ 'lib.es2021.string.d.ts',
+ 'lib.es2021.weakref.d.ts',
+ 'lib.es2021.intl.d.ts',
+ 'lib.es2022.array.d.ts',
+ 'lib.es2022.error.d.ts',
+ 'lib.es2022.intl.d.ts',
+ 'lib.es2022.object.d.ts',
+ 'lib.es2022.sharedmemory.d.ts',
+ 'lib.es2022.string.d.ts',
+ 'lib.es2022.regexp.d.ts',
+ 'lib.es2023.array.d.ts'
];
static readonly TYPED_ARRAYS = [
@@ -294,98 +225,89 @@ export class TsUtils {
'Float32Array',
'Float64Array',
'BigInt64Array',
- 'BigUint64Array',
- ]
+ 'BigUint64Array'
+ ];
- static readonly ARKTS_IGNORE_DIRS = ['node_modules', 'oh_modules', 'build', '.preview'];
+ static readonly ARKTS_IGNORE_DIRS_NO_OH_MODULES = ['node_modules', 'build', '.preview'];
+ static readonly ARKTS_IGNORE_DIRS_OH_MODULES = 'oh_modules';
+ static readonly ARKTS_IGNORE_DIRS = [
+ ...TsUtils.ARKTS_IGNORE_DIRS_NO_OH_MODULES,
+ TsUtils.ARKTS_IGNORE_DIRS_OH_MODULES
+ ];
static readonly ARKTS_IGNORE_FILES = ['hvigorfile.ts'];
- constructor(private tsTypeChecker: ts.TypeChecker, private testMode: boolean) {
- }
+ constructor(
+ private readonly tsTypeChecker: ts.TypeChecker,
+ private readonly testMode: boolean
+ ) {}
- public isTypedArray(tsType: ts.TypeNode | undefined): boolean {
- if (tsType === undefined || !ts.isTypeReferenceNode(tsType)) {
- return false;
- }
- return TsUtils.TYPED_ARRAYS.includes(this.entityNameToString(tsType.typeName));
- }
-
- public isType(tsType: ts.TypeNode | undefined, checkType: string): boolean {
+ isType(tsType: ts.TypeNode | undefined, checkType: string): boolean {
if (tsType === undefined || !ts.isTypeReferenceNode(tsType)) {
return false;
}
return this.entityNameToString(tsType.typeName) === checkType;
}
- public entityNameToString(name: ts.EntityName): string {
+ entityNameToString(name: ts.EntityName): string {
if (ts.isIdentifier(name)) {
return name.escapedText.toString();
- } else {
- return this.entityNameToString(name.left) + this.entityNameToString(name.right);
}
+ return this.entityNameToString(name.left) + this.entityNameToString(name.right);
}
- public isNumberType(tsType: ts.Type): boolean {
+ static isNumberType(tsType: ts.Type): boolean {
if (tsType.isUnion()) {
- for (let tsCompType of tsType.types) {
- if ((tsCompType.flags & ts.TypeFlags.NumberLike) === 0) return false;
+ for (const tsCompType of tsType.types) {
+ if ((tsCompType.flags & ts.TypeFlags.NumberLike) === 0) {
+ return false;
+ }
}
return true;
}
return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0;
}
- public isBooleanType(tsType: ts.Type): boolean {
+ static isBooleanType(tsType: ts.Type): boolean {
return (tsType.getFlags() & ts.TypeFlags.BooleanLike) !== 0;
}
- public isStringLikeType(tsType: ts.Type): boolean {
+ static isStringLikeType(tsType: ts.Type): boolean {
if (tsType.isUnion()) {
- for (let tsCompType of tsType.types) {
- if ((tsCompType.flags & ts.TypeFlags.StringLike) === 0) return false;
+ for (const tsCompType of tsType.types) {
+ if ((tsCompType.flags & ts.TypeFlags.StringLike) === 0) {
+ return false;
+ }
}
return true;
}
return (tsType.getFlags() & ts.TypeFlags.StringLike) !== 0;
}
- public isStringType(type: ts.Type): boolean {
+ static isStringType(type: ts.Type): boolean {
return (type.getFlags() & ts.TypeFlags.String) !== 0;
}
- public isPrimitiveEnumType(type: ts.Type, primitiveType: ts.TypeFlags): boolean {
- const isNonPrimitive = (type.flags & ts.TypeFlags.NonPrimitive) !== 0;
- if (!this.isEnumType(type) || !type.isUnion() || isNonPrimitive) {
- return false;
- }
- for (const t of type.types) {
- if ((t.flags & primitiveType) === 0) {
- return false;
- }
- }
- return true;
- }
-
- public isPrimitiveEnumMemberType(type: ts.Type, primitiveType: ts.TypeFlags): boolean {
+ static isPrimitiveEnumMemberType(type: ts.Type, primitiveType: ts.TypeFlags): boolean {
const isNonPrimitive = (type.flags & ts.TypeFlags.NonPrimitive) !== 0;
- if (!this.isEnumMemberType(type) || isNonPrimitive) {
+ if (!TsUtils.isEnumMemberType(type) || isNonPrimitive) {
return false;
}
return (type.flags & primitiveType) !== 0;
}
- public unwrapParenthesizedType(tsType: ts.TypeNode): ts.TypeNode {
+ static unwrapParenthesizedType(tsType: ts.TypeNode): ts.TypeNode {
while (ts.isParenthesizedTypeNode(tsType)) {
tsType = tsType.type;
}
return tsType;
}
- public findParentIf(asExpr: ts.AsExpression): ts.IfStatement | null {
+ static findParentIf(asExpr: ts.AsExpression): ts.IfStatement | null {
let node = asExpr.parent;
while (node) {
- if (node.kind === ts.SyntaxKind.IfStatement)
+ if (node.kind === ts.SyntaxKind.IfStatement) {
return node as ts.IfStatement;
+ }
node = node.parent;
}
@@ -393,25 +315,29 @@ export class TsUtils {
return null;
}
- 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).
+ static 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) &&
+ ts.isBinaryExpression(tsParent) &&
+ isAssignmentOperator(tsParent.operatorToken) &&
tsParent.left === tsCurrentExpr
- )
+ ) {
return true;
+ }
if (
(ts.isForStatement(tsParent) || ts.isForInStatement(tsParent) || ts.isForOfStatement(tsParent)) &&
- tsParent.initializer && tsParent.initializer === tsCurrentExpr
- )
+ tsParent.initializer &&
+ tsParent.initializer === tsCurrentExpr
+ ) {
return true;
+ }
tsCurrentExpr = tsParent;
tsParent = tsParent.parent;
@@ -420,57 +346,70 @@ export class TsUtils {
return false;
}
- 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;
+ static isEnumType(tsType: ts.Type): boolean {
+ // when type equals `typeof `, only symbol contains information about it's type.
+ const isEnumSymbol = tsType.symbol && this.isEnum(tsType.symbol);
+ // otherwise, we should analyze flags of the type itself
+ const isEnumType = !!(tsType.flags & ts.TypeFlags.Enum) || !!(tsType.flags & ts.TypeFlags.EnumLiteral);
+ return isEnumSymbol || isEnumType;
}
- 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.
+ static isEnum(tsSymbol: ts.Symbol): boolean {
+ return !!(tsSymbol.flags & ts.SymbolFlags.Enum);
+ }
+
+ static 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;
}
- public isObjectLiteralType(tsType: ts.Type): boolean {
+ static isObjectLiteralType(tsType: ts.Type): boolean {
return tsType.symbol && (tsType.symbol.flags & ts.SymbolFlags.ObjectLiteral) !== 0;
}
- public isNumberLikeType(tsType: ts.Type): boolean {
+ static isNumberLikeType(tsType: ts.Type): boolean {
return (tsType.getFlags() & ts.TypeFlags.NumberLike) !== 0;
}
- public hasModifier(tsModifiers: readonly ts.Modifier[] | undefined, tsModifierKind: number): boolean {
+ static hasModifier(tsModifiers: readonly ts.Modifier[] | undefined, tsModifierKind: number): boolean {
// Sanity check.
- if (!tsModifiers) return false;
+ if (!tsModifiers) {
+ return false;
+ }
for (const tsModifier of tsModifiers) {
- if (tsModifier.kind === tsModifierKind) return true;
+ if (tsModifier.kind === tsModifierKind) {
+ return true;
+ }
}
return false;
}
- public unwrapParenthesized(tsExpr: ts.Expression): ts.Expression {
+ static unwrapParenthesized(tsExpr: ts.Expression): ts.Expression {
let unwrappedExpr = tsExpr;
- while (ts.isParenthesizedExpression(unwrappedExpr))
+ while (ts.isParenthesizedExpression(unwrappedExpr)) {
unwrappedExpr = unwrappedExpr.expression;
+ }
return unwrappedExpr;
}
- public followIfAliased(sym: ts.Symbol): ts.Symbol {
+ followIfAliased(sym: ts.Symbol): ts.Symbol {
if ((sym.getFlags() & ts.SymbolFlags.Alias) !== 0) {
return this.tsTypeChecker.getAliasedSymbol(sym);
}
return sym;
}
- private trueSymbolAtLocationCache = new Map();
+ private readonly trueSymbolAtLocationCache = new Map();
- public trueSymbolAtLocation(node: ts.Node): ts.Symbol | undefined {
- let cache = this.trueSymbolAtLocationCache;
- let val = cache.get(node);
+ trueSymbolAtLocation(node: ts.Node): ts.Symbol | undefined {
+ const cache = this.trueSymbolAtLocationCache;
+ const val = cache.get(node);
if (val !== undefined) {
return val !== null ? val : undefined;
}
@@ -484,141 +423,186 @@ export class TsUtils {
return sym;
}
- private isTypeDeclSyntaxKind(kind: ts.SyntaxKind) {
- return this.isStructDeclarationKind(kind) ||
+ private static isTypeDeclSyntaxKind(kind: ts.SyntaxKind): boolean {
+ return (
+ TsUtils.isStructDeclarationKind(kind) ||
kind === ts.SyntaxKind.EnumDeclaration ||
kind === ts.SyntaxKind.ClassDeclaration ||
kind === ts.SyntaxKind.InterfaceDeclaration ||
- kind === ts.SyntaxKind.TypeAliasDeclaration;
+ kind === ts.SyntaxKind.TypeAliasDeclaration
+ );
}
- 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.
+ static 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) {
const declKind = symDecl.kind;
// we relax arkts-unique-names for namespace collision with class/interface/enum/type/struct
const isNamespaceTypeCollision =
- (this.isTypeDeclSyntaxKind(declKind) && tsDeclKind === ts.SyntaxKind.ModuleDeclaration) ||
- (this.isTypeDeclSyntaxKind(tsDeclKind) && declKind === ts.SyntaxKind.ModuleDeclaration)
-
- // Don't count declarations with 'Identifier' syntax kind as those
- // usually depict declaring an object's property through assignment.
- if (declKind !== ts.SyntaxKind.Identifier && declKind !== tsDeclKind && !isNamespaceTypeCollision) return true;
+ (TsUtils.isTypeDeclSyntaxKind(declKind) && tsDeclKind === ts.SyntaxKind.ModuleDeclaration) ||
+ (TsUtils.isTypeDeclSyntaxKind(tsDeclKind) && declKind === ts.SyntaxKind.ModuleDeclaration);
+
+ /*
+ * Don't count declarations with 'Identifier' syntax kind as those
+ * usually depict declaring an object's property through assignment.
+ */
+ if (declKind !== ts.SyntaxKind.Identifier && declKind !== tsDeclKind && !isNamespaceTypeCollision) {
+ return true;
+ }
}
}
return false;
}
- public isReferenceType(tsType: ts.Type): boolean {
+ static 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
+ (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
);
}
- public isPrimitiveType(type: ts.Type): boolean {
+ static 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
+ (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 isTypeSymbol(symbol: ts.Symbol | undefined): boolean {
+ static isTypeSymbol(symbol: ts.Symbol | undefined): boolean {
return (
- !!symbol && !!symbol.flags &&
+ !!symbol &&
+ !!symbol.flags &&
((symbol.flags & ts.SymbolFlags.Class) !== 0 || (symbol.flags & ts.SymbolFlags.Interface) !== 0)
);
}
// Check whether type is generic 'Array' type defined in TypeScript standard library.
- public isGenericArrayType(tsType: ts.Type): tsType is ts.TypeReference {
+ static isGenericArrayType(tsType: ts.Type): tsType is ts.TypeReference {
return (
- this.isTypeReference(tsType) && tsType.typeArguments?.length === 1 && tsType.target.typeParameters?.length === 1 &&
+ TsUtils.isTypeReference(tsType) &&
+ tsType.typeArguments?.length === 1 &&
+ tsType.target.typeParameters?.length === 1 &&
tsType.getSymbol()?.getName() === 'Array'
);
}
+ isTypedArray(tsType: ts.Type): boolean {
+ const symbol = tsType.symbol;
+ if (!symbol) {
+ return false;
+ }
+ const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
+ return this.isGlobalSymbol(symbol) && TsUtils.TYPED_ARRAYS.includes(name);
+ }
+
+ isArray(tsType: ts.Type): boolean {
+ return TsUtils.isGenericArrayType(tsType) || this.isTypedArray(tsType);
+ }
+
+ static isTuple(tsType: ts.Type): boolean {
+ return TsUtils.isTypeReference(tsType) && !!(tsType.objectFlags & ts.ObjectFlags.Tuple);
+ }
+
// does something similar to relatedByInheritanceOrIdentical function
- public isDerivedFrom(tsType: ts.Type, checkType: CheckType): tsType is ts.TypeReference {
- if (this.isTypeReference(tsType) && tsType.target !== tsType) tsType = tsType.target;
+ isOrDerivedFrom(tsType: ts.Type, checkType: CheckType): boolean {
+ if (TsUtils.isTypeReference(tsType) && tsType.target !== tsType) {
+ tsType = tsType.target;
+ }
- const tsTypeNode = this.tsTypeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.None);
- if (checkType === CheckType.Array && (this.isGenericArrayType(tsType) || this.isTypedArray(tsTypeNode)))
- return true;
- if (checkType !== CheckType.Array && this.isType(tsTypeNode, checkType.toString()))
+ if (checkType.call(this, tsType)) {
return true;
- if (!tsType.symbol || !tsType.symbol.declarations) return false;
+ }
- for (let tsTypeDecl of tsType.symbol.declarations) {
- if (
- (!ts.isClassDeclaration(tsTypeDecl) && !ts.isInterfaceDeclaration(tsTypeDecl)) ||
- !tsTypeDecl.heritageClauses
- ) continue;
- for (let heritageClause of tsTypeDecl.heritageClauses) {
- if (this.processParentTypesCheck(heritageClause.types, checkType)) return true;
+ if (!tsType.symbol?.declarations) {
+ return false;
+ }
+
+ for (const tsTypeDecl of tsType.symbol.declarations) {
+ if (!ts.isClassDeclaration(tsTypeDecl) && !ts.isInterfaceDeclaration(tsTypeDecl)) {
+ continue;
+ }
+ if (!tsTypeDecl.heritageClauses) {
+ continue;
+ }
+ for (const heritageClause of tsTypeDecl.heritageClauses) {
+ if (this.processParentTypesCheck(heritageClause.types, checkType)) {
+ return true;
+ }
}
}
return false;
}
- public isTypeReference(tsType: ts.Type): tsType is ts.TypeReference {
+ static 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 isNullType(tsTypeNode: ts.TypeNode): boolean {
- return (ts.isLiteralTypeNode(tsTypeNode) && tsTypeNode.literal.kind === ts.SyntaxKind.NullKeyword);
+ static isNullType(tsTypeNode: ts.TypeNode): boolean {
+ return ts.isLiteralTypeNode(tsTypeNode) && tsTypeNode.literal.kind === ts.SyntaxKind.NullKeyword;
}
- public isThisOrSuperExpr(tsExpr: ts.Expression): boolean {
- return (tsExpr.kind === ts.SyntaxKind.ThisKeyword || tsExpr.kind === ts.SyntaxKind.SuperKeyword);
+ static isThisOrSuperExpr(tsExpr: ts.Expression): boolean {
+ return tsExpr.kind === ts.SyntaxKind.ThisKeyword || tsExpr.kind === ts.SyntaxKind.SuperKeyword;
}
- public isPrototypeSymbol(symbol: ts.Symbol | undefined): boolean {
- return (!!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Prototype) !== 0);
+ static isPrototypeSymbol(symbol: ts.Symbol | undefined): boolean {
+ return !!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Prototype) !== 0;
}
- public isFunctionSymbol(symbol: ts.Symbol | undefined): boolean {
- return (!!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Function) !== 0);
+ static isFunctionSymbol(symbol: ts.Symbol | undefined): boolean {
+ return !!symbol && !!symbol.flags && (symbol.flags & ts.SymbolFlags.Function) !== 0;
}
- public isInterfaceType(tsType: ts.Type | undefined): boolean {
+ static isInterfaceType(tsType: ts.Type | undefined): boolean {
return (
- !!tsType && !!tsType.symbol && !!tsType.symbol.flags &&
- (tsType.symbol.flags & ts.SymbolFlags.Interface) !== 0
+ !!tsType && !!tsType.symbol && !!tsType.symbol.flags && (tsType.symbol.flags & ts.SymbolFlags.Interface) !== 0
);
}
- public isAnyType(tsType: ts.Type): tsType is ts.TypeReference {
+ static isAnyType(tsType: ts.Type): tsType is ts.TypeReference {
return (tsType.getFlags() & ts.TypeFlags.Any) !== 0;
}
- public isUnknownType(tsType: ts.Type): boolean {
+ static isUnknownType(tsType: ts.Type): boolean {
return (tsType.getFlags() & ts.TypeFlags.Unknown) !== 0;
}
- public isUnsupportedType(tsType: ts.Type): boolean {
+ static isUnsupportedType(tsType: ts.Type): boolean {
return (
- !!tsType.flags && ((tsType.flags & ts.TypeFlags.Any) !== 0 || (tsType.flags & ts.TypeFlags.Unknown) !== 0 ||
+ !!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 {
+ static isNullableUnionType(type: ts.Type): boolean {
if (type.isUnion()) {
for (const t of type.types) {
if (!!(t.flags & ts.TypeFlags.Undefined) || !!(t.flags & ts.TypeFlags.Null)) {
@@ -630,43 +614,70 @@ export class TsUtils {
return false;
}
- public isFunctionOrMethod(tsSymbol: ts.Symbol | undefined): boolean {
+ static isFunctionOrMethod(tsSymbol: ts.Symbol | undefined): boolean {
return (
- !!tsSymbol &&
- ((tsSymbol.flags & ts.SymbolFlags.Function) !== 0 || (tsSymbol.flags & ts.SymbolFlags.Method) !== 0)
+ !!tsSymbol && ((tsSymbol.flags & ts.SymbolFlags.Function) !== 0 || (tsSymbol.flags & ts.SymbolFlags.Method) !== 0)
);
}
- public isMethodAssignment(tsSymbol: ts.Symbol | undefined): boolean {
+ static isMethodAssignment(tsSymbol: ts.Symbol | undefined): boolean {
return (
- !!tsSymbol &&
- ((tsSymbol.flags & ts.SymbolFlags.Method) !== 0 && (tsSymbol.flags & ts.SymbolFlags.Assignment) !== 0)
+ !!tsSymbol && (tsSymbol.flags & ts.SymbolFlags.Method) !== 0 && (tsSymbol.flags & ts.SymbolFlags.Assignment) !== 0
);
}
- public getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | undefined {
- if (tsSymbol && tsSymbol.declarations && tsSymbol.declarations.length > 0)
+ static getDeclaration(tsSymbol: ts.Symbol | undefined): ts.Declaration | undefined {
+ if (tsSymbol?.declarations && tsSymbol.declarations.length > 0) {
return tsSymbol.declarations[0];
+ }
return undefined;
}
- private isVarDeclaration(tsDecl: ts.Node): boolean {
+ private static isVarDeclaration(tsDecl: ts.Node): boolean {
return ts.isVariableDeclaration(tsDecl) && ts.isVariableDeclarationList(tsDecl.parent);
}
- public isValidEnumMemberInit(tsExpr: ts.Expression): boolean {
- if (this.isNumberConstantValue(tsExpr.parent as ts.EnumMember))
+ isValidEnumMemberInit(tsExpr: ts.Expression): boolean {
+ if (this.isNumberConstantValue(tsExpr.parent as ts.EnumMember)) {
return true;
- if (this.isStringConstantValue(tsExpr.parent as ts.EnumMember))
+ }
+ if (this.isStringConstantValue(tsExpr.parent as ts.EnumMember)) {
return true;
+ }
return this.isCompileTimeExpression(tsExpr);
}
- public isCompileTimeExpression(tsExpr: ts.Expression): boolean {
+ private isCompileTimeExpressionHandlePropertyAccess(tsExpr: ts.Expression): boolean {
+ if (!ts.isPropertyAccessExpression(tsExpr)) {
+ return false;
+ }
+
+ /*
+ * if enum member is in current enum declaration try to get value
+ * if it comes from another enum consider as constant
+ */
+ const propertyAccess = tsExpr;
+ if (this.isNumberConstantValue(propertyAccess)) {
+ return true;
+ }
+ const leftHandSymbol = this.trueSymbolAtLocation(propertyAccess.expression);
+ if (!leftHandSymbol) {
+ return false;
+ }
+ const decls = leftHandSymbol.getDeclarations();
+ if (!decls || decls.length !== 1) {
+ return false;
+ }
+ return ts.isEnumDeclaration(decls[0]);
+ }
+
+ isCompileTimeExpression(tsExpr: ts.Expression): boolean {
if (
ts.isParenthesizedExpression(tsExpr) ||
- (ts.isAsExpression(tsExpr) && tsExpr.type.kind === ts.SyntaxKind.NumberKeyword))
+ (ts.isAsExpression(tsExpr) && tsExpr.type.kind === ts.SyntaxKind.NumberKeyword)
+ ) {
return this.isCompileTimeExpression(tsExpr.expression);
+ }
switch (tsExpr.kind) {
case ts.SyntaxKind.PrefixUnaryExpression:
@@ -682,126 +693,123 @@ export class TsUtils {
return true;
case ts.SyntaxKind.StringLiteral:
return true;
- 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.isNumberConstantValue(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]);
- }
+ case ts.SyntaxKind.PropertyAccessExpression:
+ return this.isCompileTimeExpressionHandlePropertyAccess(tsExpr);
default:
return false;
}
}
private isPrefixUnaryExprValidEnumMemberInit(tsExpr: ts.PrefixUnaryExpression): boolean {
- return (this.isUnaryOpAllowedForEnumMemberInit(tsExpr.operator) && this.isCompileTimeExpression(tsExpr.operand));
+ return TsUtils.isUnaryOpAllowedForEnumMemberInit(tsExpr.operator) && this.isCompileTimeExpression(tsExpr.operand);
}
private isBinaryExprValidEnumMemberInit(tsExpr: ts.BinaryExpression): boolean {
return (
- this.isBinaryOpAllowedForEnumMemberInit(tsExpr.operatorToken) && this.isCompileTimeExpression(tsExpr.left) &&
+ TsUtils.isBinaryOpAllowedForEnumMemberInit(tsExpr.operatorToken) &&
+ this.isCompileTimeExpression(tsExpr.left) &&
this.isCompileTimeExpression(tsExpr.right)
);
}
private isConditionalExprValidEnumMemberInit(tsExpr: ts.ConditionalExpression): boolean {
- return (this.isCompileTimeExpression(tsExpr.whenTrue) && this.isCompileTimeExpression(tsExpr.whenFalse));
+ return this.isCompileTimeExpression(tsExpr.whenTrue) && this.isCompileTimeExpression(tsExpr.whenFalse);
}
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)
- )
+ const tsSymbol = this.tsTypeChecker.getSymbolAtLocation(tsExpr);
+ const tsDecl = TsUtils.getDeclaration(tsSymbol);
+ return (
+ !!tsDecl &&
+ ((TsUtils.isVarDeclaration(tsDecl) && TsUtils.isConst(tsDecl.parent)) || tsDecl.kind === ts.SyntaxKind.EnumMember)
);
}
- private isUnaryOpAllowedForEnumMemberInit(tsPrefixUnaryOp: ts.PrefixUnaryOperator): boolean {
+ private static isUnaryOpAllowedForEnumMemberInit(tsPrefixUnaryOp: ts.PrefixUnaryOperator): boolean {
return (
- tsPrefixUnaryOp === ts.SyntaxKind.PlusToken || tsPrefixUnaryOp === ts.SyntaxKind.MinusToken ||
+ tsPrefixUnaryOp === ts.SyntaxKind.PlusToken ||
+ tsPrefixUnaryOp === ts.SyntaxKind.MinusToken ||
tsPrefixUnaryOp === ts.SyntaxKind.TildeToken
);
}
- private isBinaryOpAllowedForEnumMemberInit(tsBinaryOp: ts.BinaryOperatorToken): boolean {
+ private static 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.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
+ tsBinaryOp.kind === ts.SyntaxKind.AmpersandToken ||
+ tsBinaryOp.kind === ts.SyntaxKind.CaretToken ||
+ tsBinaryOp.kind === ts.SyntaxKind.BarToken ||
+ tsBinaryOp.kind === ts.SyntaxKind.AmpersandAmpersandToken
);
}
- public isConst(tsNode: ts.Node): boolean {
+ static isConst(tsNode: ts.Node): boolean {
return !!(ts.getCombinedNodeFlags(tsNode) & ts.NodeFlags.Const);
}
- public isNumberConstantValue(
+ isNumberConstantValue(
tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NumericLiteral
): boolean {
+ const tsConstValue =
+ tsExpr.kind === ts.SyntaxKind.NumericLiteral ?
+ Number(tsExpr.getText()) :
+ this.tsTypeChecker.getConstantValue(tsExpr);
- const tsConstValue = (tsExpr.kind === ts.SyntaxKind.NumericLiteral) ?
- Number(tsExpr.getText()) :
- this.tsTypeChecker.getConstantValue(tsExpr);
-
- return tsConstValue !== undefined && typeof tsConstValue === 'number';
+ return tsConstValue !== undefined && typeof tsConstValue === 'number';
}
- public isIntegerConstantValue(
+ 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);
+ const tsConstValue =
+ tsExpr.kind === ts.SyntaxKind.NumericLiteral ?
+ Number(tsExpr.getText()) :
+ this.tsTypeChecker.getConstantValue(tsExpr);
return (
- tsConstValue !== undefined && typeof tsConstValue === 'number' &&
+ tsConstValue !== undefined &&
+ typeof tsConstValue === 'number' &&
tsConstValue.toFixed(0) === tsConstValue.toString()
);
}
- public isStringConstantValue(
- tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression
- ): boolean {
+ isStringConstantValue(tsExpr: ts.EnumMember | ts.PropertyAccessExpression | ts.ElementAccessExpression): boolean {
const tsConstValue = this.tsTypeChecker.getConstantValue(tsExpr);
- return (
- tsConstValue !== undefined && typeof tsConstValue === 'string'
- );
+ return tsConstValue !== undefined && typeof tsConstValue === 'string';
}
// 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;
+ relatedByInheritanceOrIdentical(typeA: ts.Type, typeB: ts.Type): boolean {
+ if (TsUtils.isTypeReference(typeA) && typeA.target !== typeA) {
+ typeA = typeA.target;
+ }
+ if (TsUtils.isTypeReference(typeB) && typeB.target !== typeB) {
+ typeB = typeB.target;
+ }
if (typeA === typeB || this.isObject(typeB)) {
return true;
}
- if (!typeA.symbol || !typeA.symbol.declarations) {
+ if (!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) {
- let processInterfaces = typeA.isClass() ? (heritageClause.token !== ts.SyntaxKind.ExtendsKeyword) : true;
- if (this.processParentTypes(heritageClause.types, typeB, processInterfaces)) return true;
+ for (const typeADecl of typeA.symbol.declarations) {
+ if ((!ts.isClassDeclaration(typeADecl) && !ts.isInterfaceDeclaration(typeADecl)) || !typeADecl.heritageClauses) {
+ continue;
+ }
+ for (const heritageClause of typeADecl.heritageClauses) {
+ const processInterfaces = typeA.isClass() ? heritageClause.token !== ts.SyntaxKind.ExtendsKeyword : true;
+ if (this.processParentTypes(heritageClause.types, typeB, processInterfaces)) {
+ return true;
+ }
}
}
@@ -809,20 +817,25 @@ export class TsUtils {
}
// return true if two class types are not related by inheritance and structural identity check is needed
- public needToDeduceStructuralIdentity(lhsType: ts.Type, rhsType: ts.Type, rhsExpr: ts.Expression,
- allowPromotion: boolean = false): boolean {
+ needToDeduceStructuralIdentity(
+ lhsType: ts.Type,
+ rhsType: ts.Type,
+ rhsExpr: ts.Expression,
+ allowPromotion: boolean = false
+ ): boolean {
// Compare non-nullable version of types.
- lhsType = this.getNonNullableType(lhsType);
- rhsType = this.getNonNullableType(rhsType);
-
+ lhsType = TsUtils.getNonNullableType(lhsType);
+ rhsType = TsUtils.getNonNullableType(rhsType);
if (this.isLibraryType(lhsType)) {
return false;
}
-
if (this.isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) {
return false;
}
-
+ // #14569: Check for Function type.
+ if (this.areCompatibleFunctionals(lhsType, rhsType)) {
+ return false;
+ }
if (rhsType.isUnion()) {
// Each Class/Interface of the RHS union type must be compatible with LHS type.
for (const compType of rhsType.types) {
@@ -832,7 +845,6 @@ export class TsUtils {
}
return false;
}
-
if (lhsType.isUnion()) {
// RHS type needs to be compatible with at least one type of the LHS union.
for (const compType of lhsType.types) {
@@ -842,17 +854,17 @@ export class TsUtils {
}
return true;
}
-
- let res = lhsType.isClassOrInterface() && rhsType.isClassOrInterface() && !this.relatedByInheritanceOrIdentical(rhsType, lhsType);
-
+ let res =
+ lhsType.isClassOrInterface() &&
+ rhsType.isClassOrInterface() &&
+ !this.relatedByInheritanceOrIdentical(rhsType, lhsType);
if (allowPromotion) {
res &&= !this.relatedByInheritanceOrIdentical(lhsType, rhsType);
}
-
return res;
}
- public hasPredecessor(node: ts.Node, predicate: (node: ts.Node) => boolean): boolean {
+ static hasPredecessor(node: ts.Node, predicate: (node: ts.Node) => boolean): boolean {
let parent = node.parent;
while (parent !== undefined) {
if (predicate(parent)) {
@@ -863,64 +875,87 @@ export class TsUtils {
return false;
}
- private processParentTypes(parentTypes: ts.NodeArray, typeB: ts.Type, processInterfaces: boolean): boolean {
- for (let baseTypeExpr of parentTypes) {
+ private processParentTypes(
+ parentTypes: ts.NodeArray,
+ typeB: ts.Type,
+ processInterfaces: boolean
+ ): boolean {
+ for (const baseTypeExpr of parentTypes) {
let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr);
- if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target;
- if (baseType && (baseType.isClass() !== processInterfaces) && this.relatedByInheritanceOrIdentical(baseType, typeB)) {
+ if (TsUtils.isTypeReference(baseType) && baseType.target !== baseType) {
+ baseType = baseType.target;
+ }
+ if (
+ baseType &&
+ baseType.isClass() !== processInterfaces &&
+ this.relatedByInheritanceOrIdentical(baseType, typeB)
+ ) {
return true;
}
}
return false;
}
- private processParentTypesCheck(parentTypes: ts.NodeArray, checkType: CheckType): boolean {
- for (let baseTypeExpr of parentTypes) {
+ private processParentTypesCheck(
+ parentTypes: ts.NodeArray,
+ checkType: CheckType
+ ): boolean {
+ for (const baseTypeExpr of parentTypes) {
let baseType = this.tsTypeChecker.getTypeAtLocation(baseTypeExpr);
- if (this.isTypeReference(baseType) && baseType.target !== baseType) baseType = baseType.target;
- if (baseType && this.isDerivedFrom(baseType, checkType)) return true;
+ if (TsUtils.isTypeReference(baseType) && baseType.target !== baseType) {
+ baseType = baseType.target;
+ }
+ if (baseType && this.isOrDerivedFrom(baseType, checkType)) {
+ return true;
+ }
}
return false;
}
- public isObject(tsType: ts.Type): boolean {
+ isObject(tsType: ts.Type): boolean {
if (!tsType) {
return false;
}
- if (tsType.symbol && (tsType.isClassOrInterface() && tsType.symbol.name === 'Object')) {
+ if (tsType.symbol && tsType.isClassOrInterface() && tsType.symbol.name === 'Object') {
return true;
}
- let node = this.tsTypeChecker.typeToTypeNode(tsType, undefined, undefined);
+ const node = this.tsTypeChecker.typeToTypeNode(tsType, undefined, undefined);
return node !== undefined && node.kind === ts.SyntaxKind.ObjectKeyword;
}
- public isCallToFunctionWithOmittedReturnType(tsExpr: ts.Expression): boolean {
+ isCallToFunctionWithOmittedReturnType(tsExpr: ts.Expression): boolean {
if (ts.isCallExpression(tsExpr)) {
- let tsCallSignature = this.tsTypeChecker.getResolvedSignature(tsExpr);
+ const tsCallSignature = this.tsTypeChecker.getResolvedSignature(tsExpr);
if (tsCallSignature) {
const tsSignDecl = tsCallSignature.getDeclaration();
// `tsSignDecl` is undefined when `getResolvedSignature` returns `unknownSignature`
- if (!tsSignDecl || !tsSignDecl.type) return true;
+ if (!tsSignDecl?.type) {
+ return true;
+ }
}
}
return false;
}
- private hasReadonlyFields(type: ts.Type): boolean {
- if (type.symbol.members === undefined) return false; // No members -> no readonly fields
+ private static hasReadonlyFields(type: ts.Type): boolean {
+ // No members -> no readonly fields
+ if (type.symbol.members === undefined) {
+ return false;
+ }
let result: boolean = false;
- type.symbol.members.forEach((value, key) => {
+ type.symbol.members.forEach((value) => {
if (
- value.declarations !== undefined && value.declarations.length > 0 &&
+ 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)) {
+ // TSC 4.2 doesn't have 'ts.getModifiers()' method
+ const propmMods = value.declarations[0].modifiers;
+ if (TsUtils.hasModifier(propmMods, ts.SyntaxKind.ReadonlyKeyword)) {
result = true;
- return;
}
}
});
@@ -928,55 +963,66 @@ export class TsUtils {
return result;
}
- private hasDefaultCtor(type: ts.Type): boolean {
- if (type.symbol.members === undefined) return true; // No members -> no explicite constructors -> there is default ctor
+ private static hasDefaultCtor(type: ts.Type): boolean {
+ // No members -> no explicite constructors -> there is default ctor
+ if (type.symbol.members === undefined) {
+ return true;
+ }
- let hasCtor: boolean = false; // has any constructor
- let hasDefaultCtor: boolean = false; // has default constructor
+ let hasCtor: boolean = false;
+ let hasDefaultCtor: boolean = false;
- type.symbol.members.forEach((value, key) => {
+ type.symbol.members.forEach((value) => {
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;
+ const 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.
+ // Has no any explicit constructor -> has implicit default constructor.
+ return !hasCtor || hasDefaultCtor;
}
- private isAbstractClass(type: ts.Type): boolean {
+ private static 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))
+ const declClass = type.symbol.declarations[0] as ts.ClassDeclaration;
+ // TSC 4.2 doesn't have 'ts.getModifiers()' method
+ const classMods = declClass.modifiers;
+ if (TsUtils.hasModifier(classMods, ts.SyntaxKind.AbstractKeyword)) {
return true;
+ }
}
return false;
}
- public validateObjectLiteralType(type: ts.Type | undefined): boolean {
- if (!type) return false;
+ static validateObjectLiteralType(type: ts.Type | undefined): boolean {
+ if (!type) {
+ return false;
+ }
- type = this.getTargetType(type);
+ type = TsUtils.getTargetType(type);
return (
- type && type.isClassOrInterface() && this.hasDefaultCtor(type) &&
- !this.hasReadonlyFields(type) && !this.isAbstractClass(type)
+ type &&
+ type.isClassOrInterface() &&
+ TsUtils.hasDefaultCtor(type) &&
+ !TsUtils.hasReadonlyFields(type) &&
+ !TsUtils.isAbstractClass(type)
);
}
- public hasMethods(type: ts.Type): boolean {
+ hasMethods(type: ts.Type): boolean {
const properties = this.tsTypeChecker.getPropertiesOfType(type);
if (properties?.length) {
for (const prop of properties) {
- if (prop.getFlags() & ts.SymbolFlags.Method) return true;
+ if (prop.getFlags() & ts.SymbolFlags.Method) {
+ return true;
+ }
}
}
return false;
@@ -986,57 +1032,66 @@ export class TsUtils {
const properties = this.tsTypeChecker.getPropertiesOfType(type);
if (properties?.length) {
for (const prop of properties) {
- if (prop.name === name) return prop;
+ if (prop.name === name) {
+ return prop;
+ }
}
}
return undefined;
}
- public checkTypeSet(uType: ts.Type, predicate: (t: ts.Type) => boolean): boolean {
- if (!uType.isUnionOrIntersection()) {
- return predicate(uType);
+ checkTypeSet(typeSet: ts.Type, predicate: CheckType): boolean {
+ if (!typeSet.isUnionOrIntersection()) {
+ return predicate.call(this, typeSet);
}
- for (let elemType of uType.types) {
- if (!this.checkTypeSet(elemType, predicate)) {
- return false;
+ for (const elemType of typeSet.types) {
+ if (this.checkTypeSet(elemType, predicate)) {
+ return true;
}
}
- return true;
+ return false;
}
- private getNonNullableType(t: ts.Type): ts.Type {
- if (this.isNullableUnionType(t)) {
+ static getNonNullableType(t: ts.Type): ts.Type {
+ if (TsUtils.isNullableUnionType(t)) {
return t.getNonNullableType();
}
return t;
}
- public isObjectLiteralAssignable(lhsType: ts.Type | undefined, rhsExpr: ts.ObjectLiteralExpression): boolean {
+ private isObjectLiteralAssignableToUnion(lhsType: ts.UnionType, rhsExpr: ts.ObjectLiteralExpression): boolean {
+ for (const compType of lhsType.types) {
+ if (this.isObjectLiteralAssignable(compType, rhsExpr)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ isObjectLiteralAssignable(lhsType: ts.Type | undefined, rhsExpr: ts.ObjectLiteralExpression): boolean {
if (lhsType === undefined) {
return false;
}
-
// Always check with the non-nullable variant of lhs type.
- lhsType = this.getNonNullableType(lhsType);
-
- if (lhsType.isUnion()) {
- for (const compType of lhsType.types) {
- if (this.isObjectLiteralAssignable(compType, rhsExpr)) {
- return true;
- }
- }
+ lhsType = TsUtils.getNonNullableType(lhsType);
+ if (lhsType.isUnion() && this.isObjectLiteralAssignableToUnion(lhsType, rhsExpr)) {
+ return true;
}
- // Allow initializing with anything when the type
- // originates from the library.
- if (this.isAnyType(lhsType) || this.isLibraryType(lhsType)) {
+ /*
+ * Allow initializing with anything when the type
+ * originates from the library.
+ */
+ if (TsUtils.isAnyType(lhsType) || this.isLibraryType(lhsType)) {
return true;
}
- // issue 13412:
- // Allow initializing with a dynamic object when the LHS type
- // is primitive or defined in standard library.
+ /*
+ * issue 13412:
+ * Allow initializing with a dynamic object when the LHS type
+ * is primitive or defined in standard library.
+ */
if (this.isDynamicObjectAssignedToStdType(lhsType, rhsExpr)) {
return true;
}
@@ -1050,29 +1105,33 @@ export class TsUtils {
}
}
- // Allow initializing Record objects with object initializer.
- // Record supports any type for a its value, but the key value
- // must be either a string or number literal.
+ /*
+ * Allow initializing Record objects with object initializer.
+ * Record supports any type for a its value, but the key value
+ * must be either a string or number literal.
+ */
if (this.isStdRecordType(lhsType)) {
- return this.validateRecordObjectKeys(rhsExpr);
+ return TsUtils.validateRecordObjectKeys(rhsExpr);
}
-
- return this.validateObjectLiteralType(lhsType) && !this.hasMethods(lhsType) && this.validateFields(lhsType, rhsExpr);
+ return (
+ TsUtils.validateObjectLiteralType(lhsType) && !this.hasMethods(lhsType) && this.validateFields(lhsType, rhsExpr)
+ );
}
private isDynamicObjectAssignedToStdType(lhsType: ts.Type, rhsExpr: ts.Expression): boolean {
- if (this.isStdLibraryType(lhsType) || this.isPrimitiveType(lhsType)) {
- let rhsSym = ts.isCallExpression(rhsExpr)
+ if (TsUtils.isStdLibraryType(lhsType) || TsUtils.isPrimitiveType(lhsType)) {
+ const rhsSym = ts.isCallExpression(rhsExpr)
? this.getSymbolOfCallExpression(rhsExpr)
: this.tsTypeChecker.getSymbolAtLocation(rhsExpr);
- if (rhsSym && this.isLibrarySymbol(rhsSym))
+ if (rhsSym && this.isLibrarySymbol(rhsSym)) {
return true;
+ }
}
return false;
}
- isFunctionalType(type: ts.Type): boolean {
+ static isFunctionalType(type: ts.Type): boolean {
const callSigns = type.getCallSignatures();
return callSigns && callSigns.length > 0;
}
@@ -1084,181 +1143,194 @@ export class TsUtils {
return false;
}
}
- };
+ }
return true;
}
private validateField(type: ts.Type, prop: ts.PropertyAssignment): boolean {
- const propName = prop.name.getText();
+ const propNameSymbol = this.tsTypeChecker.getSymbolAtLocation(prop.name);
+ const propName = propNameSymbol?.escapedName.toString() ?? prop.name.getText();
const propSym = this.findProperty(type, propName);
- if (!propSym || !propSym.declarations?.length) {
+ if (!propSym?.declarations?.length) {
return false;
}
const propType = this.tsTypeChecker.getTypeOfSymbolAtLocation(propSym, propSym.declarations[0]);
- const initExpr = this.unwrapParenthesized(prop.initializer);
+ const initExpr = TsUtils.unwrapParenthesized(prop.initializer);
if (ts.isObjectLiteralExpression(initExpr)) {
if (!this.isObjectLiteralAssignable(propType, initExpr)) {
return false;
}
- } else {
+ } else if (
+ this.needToDeduceStructuralIdentity(propType, this.tsTypeChecker.getTypeAtLocation(initExpr), initExpr)
+ ) {
// Only check for structural sub-typing.
- if (this.needToDeduceStructuralIdentity(propType, this.tsTypeChecker.getTypeAtLocation(initExpr), initExpr)) {
- return false;
- }
+ return false;
}
return true;
}
- validateRecordObjectKeys(objectLiteral: ts.ObjectLiteralExpression): boolean {
+ static validateRecordObjectKeys(objectLiteral: ts.ObjectLiteralExpression): boolean {
for (const prop of objectLiteral.properties) {
- if (!prop.name || (!ts.isStringLiteral(prop.name) && !ts.isNumericLiteral(prop.name))) return false;
+ if (!prop.name || (!ts.isStringLiteral(prop.name) && !ts.isNumericLiteral(prop.name))) {
+ return false;
+ }
}
return true;
}
- getTargetType(type: ts.Type): ts.Type {
- return (type.getFlags() & ts.TypeFlags.Object) &&
- (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference ? (type as ts.TypeReference).target : type;
+ static getTargetType(type: ts.Type): ts.Type {
+ return type.getFlags() & ts.TypeFlags.Object && (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference ?
+ (type as ts.TypeReference).target :
+ type;
+ }
+
+ private static isSupportedTypeNodeKind(kind: ts.SyntaxKind): boolean {
+ return (
+ kind !== ts.SyntaxKind.AnyKeyword &&
+ kind !== ts.SyntaxKind.UnknownKeyword &&
+ kind !== ts.SyntaxKind.SymbolKeyword &&
+ kind !== ts.SyntaxKind.IndexedAccessType &&
+ kind !== ts.SyntaxKind.ConditionalType &&
+ kind !== ts.SyntaxKind.MappedType &&
+ kind !== ts.SyntaxKind.InferType
+ );
+ }
+
+ private isSupportedTypeHandleUnionTypeNode(typeNode: ts.UnionTypeNode): boolean {
+ for (const unionTypeElem of typeNode.types) {
+ if (!this.isSupportedType(unionTypeElem)) {
+ return false;
+ }
+ }
+ return true;
}
- private isSupportedTypeNodeKind(kind: ts.SyntaxKind): boolean {
- return kind !== ts.SyntaxKind.AnyKeyword && kind !== ts.SyntaxKind.UnknownKeyword &&
- kind !== ts.SyntaxKind.SymbolKeyword && kind !== ts.SyntaxKind.IndexedAccessType &&
- kind !== ts.SyntaxKind.ConditionalType && kind !== ts.SyntaxKind.MappedType &&
- kind !== ts.SyntaxKind.InferType;
+ private isSupportedTypeHandleTupleTypeNode(typeNode: ts.TupleTypeNode): boolean {
+ for (const elem of typeNode.elements) {
+ if (ts.isTypeNode(elem) && !this.isSupportedType(elem)) {
+ return false;
+ }
+ if (ts.isNamedTupleMember(elem) && !this.isSupportedType(elem.type)) {
+ return false;
+ }
+ }
+ return true;
}
- public isSupportedType(typeNode: ts.TypeNode): boolean {
- if (ts.isParenthesizedTypeNode(typeNode)) return this.isSupportedType(typeNode.type);
+ isSupportedType(typeNode: ts.TypeNode): boolean {
+ if (ts.isParenthesizedTypeNode(typeNode)) {
+ return this.isSupportedType(typeNode.type);
+ }
- if (ts.isArrayTypeNode(typeNode)) return this.isSupportedType(typeNode.elementType);
+ if (ts.isArrayTypeNode(typeNode)) {
+ return this.isSupportedType(typeNode.elementType);
+ }
if (ts.isTypeReferenceNode(typeNode) && typeNode.typeArguments) {
- for (const typeArg of typeNode.typeArguments)
- if (!this.isSupportedType(typeArg)) return false;
+ for (const typeArg of typeNode.typeArguments) {
+ if (!this.isSupportedType(typeArg)) {
+ return false;
+ }
+ }
return true;
}
if (ts.isUnionTypeNode(typeNode)) {
- for (const unionTypeElem of typeNode.types)
- if (!this.isSupportedType(unionTypeElem)) return false;
- return true;
+ return this.isSupportedTypeHandleUnionTypeNode(typeNode);
}
if (ts.isTupleTypeNode(typeNode)) {
- for (const elem of typeNode.elements) {
- if (ts.isTypeNode(elem) && !this.isSupportedType(elem)) {
- return false;
- }
- if (ts.isNamedTupleMember(elem) && !this.isSupportedType(elem.type)) {
- return false;
- }
- }
- return true;
+ return this.isSupportedTypeHandleTupleTypeNode(typeNode);
}
- return !ts.isTypeLiteralNode(typeNode) && !ts.isTypeQueryNode(typeNode) &&
- !ts.isIntersectionTypeNode(typeNode) && this.isSupportedTypeNodeKind(typeNode.kind);
+ return (
+ !ts.isTypeLiteralNode(typeNode) &&
+ !ts.isIntersectionTypeNode(typeNode) &&
+ TsUtils.isSupportedTypeNodeKind(typeNode.kind)
+ );
}
- public isStruct(symbol: ts.Symbol) {
+ static isStruct(symbol: ts.Symbol): boolean {
if (!symbol.declarations) {
return false;
}
for (const decl of symbol.declarations) {
- if (this.isStructDeclaration(decl)) {
+ if (TsUtils.isStructDeclaration(decl)) {
return true;
}
}
return false;
}
- public isStructDeclarationKind(kind: ts.SyntaxKind) {
+ static isStructDeclarationKind(kind: ts.SyntaxKind): boolean {
return LinterConfig.tsSyntaxKindNames[kind] === 'StructDeclaration';
}
- public isStructDeclaration(node: ts.Node) {
- return this.isStructDeclarationKind(node.kind);
+ static isStructDeclaration(node: ts.Node): boolean {
+ return TsUtils.isStructDeclarationKind(node.kind);
}
- public isStructObjectInitializer(objectLiteral: ts.ObjectLiteralExpression): boolean {
+ isStructObjectInitializer(objectLiteral: ts.ObjectLiteralExpression): boolean {
if (ts.isCallLikeExpression(objectLiteral.parent)) {
const signature = this.tsTypeChecker.getResolvedSignature(objectLiteral.parent);
const signDecl = signature?.declaration;
- return !!signDecl && ts.isConstructorDeclaration(signDecl) && this.isStructDeclaration(signDecl.parent);
+ return !!signDecl && ts.isConstructorDeclaration(signDecl) && TsUtils.isStructDeclaration(signDecl.parent);
}
return false;
}
- public getParentSymbolName(symbol: ts.Symbol): string | undefined {
+ 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 dotPosition === -1 ? undefined : name.substring(0, dotPosition);
}
- public isGlobalSymbol(symbol: ts.Symbol): boolean {
- let parentName = this.getParentSymbolName(symbol);
+ isGlobalSymbol(symbol: ts.Symbol): boolean {
+ const parentName = this.getParentSymbolName(symbol);
return !parentName || parentName === 'global';
}
- public isStdObjectAPI(symbol: ts.Symbol): boolean {
- let parentName = this.getParentSymbolName(symbol);
- return !!parentName && (parentName === 'Object');
- }
-
- 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');
- }
-
- public isStdArrayBufferAPI(symbol: ts.Symbol): boolean {
- let parentName = this.getParentSymbolName(symbol);
- return !!parentName && (parentName === 'ArrayBuffer');
+ isSymbolAPI(symbol: ts.Symbol): boolean {
+ const parentName = this.getParentSymbolName(symbol);
+ return !!parentName && (parentName === 'Symbol' || parentName === 'SymbolConstructor');
}
- public isStdSymbol(symbol: ts.Symbol): boolean {
+ isStdSymbol(symbol: ts.Symbol): boolean {
const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
- return name === 'Symbol';
+ return name === 'Symbol' && this.isGlobalSymbol(symbol);
}
- public isStdSymbolAPI(symbol: ts.Symbol): boolean {
- let parentName = this.getParentSymbolName(symbol);
- return !!parentName && parentName === 'Symbol';
+ isSymbolIterator(symbol: ts.Symbol): boolean {
+ return this.isSymbolAPI(symbol) && symbol.name === 'iterator';
}
- public isDefaultImport(importSpec: ts.ImportSpecifier): boolean {
+ static 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)
+ static 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();
}
- public getEndPos(nodeOrComment: ts.Node | ts.CommentRange): number {
- return (nodeOrComment.kind === ts.SyntaxKind.SingleLineCommentTrivia || nodeOrComment.kind === ts.SyntaxKind.MultiLineCommentTrivia)
+ static 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();
}
- public isStdRecordType(type: ts.Type): boolean {
- // In TypeScript, 'Record' is defined as type alias to a mapped type.
- // Thus, it should have 'aliasSymbol' and 'target' properties. The 'target'
- // in this case will resolve to origin 'Record' symbol.
+ isStdRecordType(type: ts.Type): boolean {
+ /*
+ * In TypeScript, 'Record' is defined as type alias to a mapped type.
+ * Thus, it should have 'aliasSymbol' and 'target' properties. The 'target'
+ * in this case will resolve to origin 'Record' symbol.
+ */
if (type.aliasSymbol) {
const target = (type as ts.TypeReference).target;
if (target) {
@@ -1270,22 +1342,31 @@ export class TsUtils {
return false;
}
- public isStdPartialType(type: ts.Type): boolean {
+ isStdErrorType(type: ts.Type): boolean {
+ const symbol = type.symbol;
+ if (!symbol) {
+ return false;
+ }
+ const name = this.tsTypeChecker.getFullyQualifiedName(symbol);
+ return name === 'Error' && this.isGlobalSymbol(symbol);
+ }
+
+ isStdPartialType(type: ts.Type): boolean {
const sym = type.aliasSymbol;
return !!sym && sym.getName() === 'Partial' && this.isGlobalSymbol(sym);
}
- public isStdRequiredType(type: ts.Type): boolean {
+ isStdRequiredType(type: ts.Type): boolean {
const sym = type.aliasSymbol;
return !!sym && sym.getName() === 'Required' && this.isGlobalSymbol(sym);
}
- public isStdReadonlyType(type: ts.Type): boolean {
+ isStdReadonlyType(type: ts.Type): boolean {
const sym = type.aliasSymbol;
return !!sym && sym.getName() === 'Readonly' && this.isGlobalSymbol(sym);
}
- public isLibraryType(type: ts.Type): boolean {
+ isLibraryType(type: ts.Type): boolean {
const nonNullableType = type.getNonNullableType();
if (nonNullableType.isUnion()) {
for (const componentType of nonNullableType.types) {
@@ -1298,46 +1379,56 @@ export class TsUtils {
return this.isLibrarySymbol(nonNullableType.aliasSymbol ?? nonNullableType.getSymbol());
}
- public hasLibraryType(node: ts.Node): boolean {
+ hasLibraryType(node: ts.Node): boolean {
return this.isLibraryType(this.tsTypeChecker.getTypeAtLocation(node));
}
- public isLibrarySymbol(sym: ts.Symbol | undefined) {
- if (sym && sym.declarations && sym.declarations.length > 0) {
+ isLibrarySymbol(sym: ts.Symbol | undefined): boolean {
+ if (sym?.declarations && sym.declarations.length > 0) {
const srcFile = sym.declarations[0].getSourceFile();
if (!srcFile) {
return false;
}
- const fileName = srcFile.fileName
- // Symbols from both *.ts and *.d.ts files should obey interop rules.
- // We disable such behavior for *.ts files in the test mode due to lack of 'ets'
- // extension support.
+ const fileName = srcFile.fileName;
+
+ /*
+ * Symbols from both *.ts and *.d.ts files should obey interop rules.
+ * We disable such behavior for *.ts files in the test mode due to lack of 'ets'
+ * extension support.
+ */
const ext = path.extname(fileName).toLowerCase();
const isThirdPartyCode =
- TsUtils.ARKTS_IGNORE_DIRS.some(ignore => pathContainsDirectory(path.normalize(fileName), ignore)) ||
- TsUtils.ARKTS_IGNORE_FILES.some(ignore => path.basename(fileName) === ignore);
- const isEts = (ext === '.ets');
- const isTs = (ext === '.ts' && !srcFile.isDeclarationFile);
+ TsUtils.ARKTS_IGNORE_DIRS.some((ignore) => {
+ return pathContainsDirectory(path.normalize(fileName), ignore);
+ }) ||
+ TsUtils.ARKTS_IGNORE_FILES.some((ignore) => {
+ return path.basename(fileName) === ignore;
+ });
+ const isEts = ext === '.ets';
+ const isTs = ext === '.ts' && !srcFile.isDeclarationFile;
const isStatic = (isEts || (isTs && this.testMode)) && !isThirdPartyCode;
- // We still need to confirm support for certain API from the
- // TypeScript standard library in ArkTS. Thus, for now do not
- // count standard library modules.
- return !isStatic &&
- !TsUtils.STANDARD_LIBRARIES.includes(path.basename(srcFile.fileName).toLowerCase());
+ const isStdLib = TsUtils.STANDARD_LIBRARIES.includes(path.basename(fileName).toLowerCase());
+
+ /*
+ * We still need to confirm support for certain API from the
+ * TypeScript standard library in ArkTS. Thus, for now do not
+ * count standard library modules.
+ */
+ return !isStatic && !isStdLib;
}
return false;
}
- public isStdFunctionType(type: ts.Type) {
+ isStdFunctionType(type: ts.Type): boolean {
const sym = type.getSymbol();
- return sym && sym.getName() === 'Function' && this.isGlobalSymbol(sym);
+ return !!sym && sym.getName() === 'Function' && this.isGlobalSymbol(sym);
}
- public getScriptKind(srcFile: ts.SourceFile): ts.ScriptKind {
- const fileName = srcFile.fileName
- const ext = path.extname(fileName)
+ static getScriptKind(srcFile: ts.SourceFile): ts.ScriptKind {
+ const fileName = srcFile.fileName;
+ const ext = path.extname(fileName);
switch (ext.toLowerCase()) {
case ts.Extension.Js:
return ts.ScriptKind.JS;
@@ -1354,40 +1445,43 @@ export class TsUtils {
}
}
- public isStdLibraryType(type: ts.Type): boolean {
- return this.isStdLibrarySymbol(type.aliasSymbol ?? type.getSymbol());
+ static isStdLibraryType(type: ts.Type): boolean {
+ return TsUtils.isStdLibrarySymbol(type.aliasSymbol ?? type.getSymbol());
}
- public isStdLibrarySymbol(sym: ts.Symbol | undefined) {
- if (sym && sym.declarations && sym.declarations.length > 0) {
+ static isStdLibrarySymbol(sym: ts.Symbol | undefined): boolean {
+ if (sym?.declarations && sym.declarations.length > 0) {
const srcFile = sym.declarations[0].getSourceFile();
- return srcFile &&
- TsUtils.STANDARD_LIBRARIES.includes(path.basename(srcFile.fileName).toLowerCase());
+ return srcFile && TsUtils.STANDARD_LIBRARIES.includes(path.basename(srcFile.fileName).toLowerCase());
}
return false;
}
- public isIntrinsicObjectType(type: ts.Type): boolean {
+ static isIntrinsicObjectType(type: ts.Type): boolean {
return !!(type.flags & ts.TypeFlags.NonPrimitive);
}
- public isDynamicType(type: ts.Type | undefined): boolean | undefined {
+ isDynamicType(type: ts.Type | undefined): boolean | undefined {
if (type === undefined) {
return false;
}
- // Return 'true' if it is an object of library type initialization, otherwise
- // return 'false' if it is not an object of standard library type one.
- // In the case of standard library type we need to determine context.
+ /*
+ * Return 'true' if it is an object of library type initialization, otherwise
+ * return 'false' if it is not an object of standard library type one.
+ * In the case of standard library type we need to determine context.
+ */
- // Check the non-nullable version of type to eliminate 'undefined' type
- // from the union type elements.
+ /*
+ * Check the non-nullable version of type to eliminate 'undefined' type
+ * from the union type elements.
+ */
type = type.getNonNullableType();
if (type.isUnion()) {
- for (let compType of type.types) {
- let isDynamic = this.isDynamicType(compType);
+ for (const compType of type.types) {
+ const isDynamic = this.isDynamicType(compType);
if (isDynamic || isDynamic === undefined) {
return isDynamic;
}
@@ -1399,36 +1493,64 @@ export class TsUtils {
return true;
}
- if (!this.isStdLibraryType(type) && !this.isIntrinsicObjectType(type) && !this.isAnyType(type)) {
+ if (!TsUtils.isStdLibraryType(type) && !TsUtils.isIntrinsicObjectType(type) && !TsUtils.isAnyType(type)) {
return false;
}
return undefined;
}
- public isObjectType(type: ts.Type): type is ts.ObjectType {
+ static isObjectType(type: ts.Type): type is ts.ObjectType {
return !!(type.flags & ts.TypeFlags.Object);
}
- private isAnonymous(type: ts.Type): boolean {
- if (this.isObjectType(type)) {
+ private static isAnonymous(type: ts.Type): boolean {
+ if (TsUtils.isObjectType(type)) {
return !!(type.objectFlags & ts.ObjectFlags.Anonymous);
}
return false;
}
- public isDynamicLiteralInitializer(expr: ts.Expression): boolean {
+ private isDynamicLiteralInitializerHandleCallExpression(callExpr: ts.CallExpression): boolean {
+ const type = this.tsTypeChecker.getTypeAtLocation(callExpr.expression);
+
+ if (TsUtils.isAnyType(type)) {
+ return true;
+ }
+
+ let sym: ts.Symbol | undefined = type.symbol;
+ if (this.isLibrarySymbol(sym)) {
+ return true;
+ }
+
+ /*
+ * #13483:
+ * x.foo({ ... }), where 'x' is exported from some library:
+ */
+ if (ts.isPropertyAccessExpression(callExpr.expression)) {
+ sym = this.trueSymbolAtLocation(callExpr.expression.expression);
+ if (sym && this.isLibrarySymbol(sym)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ isDynamicLiteralInitializer(expr: ts.Expression): boolean {
if (!ts.isObjectLiteralExpression(expr) && !ts.isArrayLiteralExpression(expr)) {
return false;
}
- // Handle nested literals:
- // { f: { ... } }
+ /*
+ * Handle nested literals:
+ * { f: { ... } }
+ */
let curNode: ts.Node = expr;
while (ts.isObjectLiteralExpression(curNode) || ts.isArrayLiteralExpression(curNode)) {
const exprType = this.tsTypeChecker.getContextualType(curNode);
- if (exprType !== undefined && !this.isAnonymous(exprType)) {
- const res = this.isDynamicType(exprType)
+ if (exprType !== undefined && !TsUtils.isAnonymous(exprType)) {
+ const res = this.isDynamicType(exprType);
if (res !== undefined) {
return res;
}
@@ -1440,40 +1562,22 @@ export class TsUtils {
}
}
- // Handle calls with literals:
- // foo({ ... })
- if (ts.isCallExpression(curNode)) {
- const callExpr = curNode as ts.CallExpression;
- const type = this.tsTypeChecker.getTypeAtLocation(callExpr.expression)
-
- if (this.isAnyType(type)) {
- return true;
- }
-
- let sym: ts.Symbol | undefined = type.symbol;
- if(this.isLibrarySymbol(sym)) {
- return true;
- }
-
- // #13483:
- // x.foo({ ... }), where 'x' is exported from some library:
- if (ts.isPropertyAccessExpression(callExpr.expression)) {
- sym = this.tsTypeChecker.getSymbolAtLocation(callExpr.expression.expression);
- if (sym && sym.getFlags() & ts.SymbolFlags.Alias) {
- sym = this.tsTypeChecker.getAliasedSymbol(sym);
- if (this.isLibrarySymbol(sym)) {
- return true;
- }
- }
- }
+ /*
+ * Handle calls with literals:
+ * foo({ ... })
+ */
+ if (ts.isCallExpression(curNode) && this.isDynamicLiteralInitializerHandleCallExpression(curNode)) {
+ return true;
}
- // Handle property assignments with literals:
- // obj.f = { ... }
+ /*
+ * Handle property assignments with literals:
+ * obj.f = { ... }
+ */
if (ts.isBinaryExpression(curNode)) {
- const binExpr = curNode as ts.BinaryExpression;
+ const binExpr = curNode;
if (ts.isPropertyAccessExpression(binExpr.left)) {
- const propAccessExpr = binExpr.left as ts.PropertyAccessExpression;
+ const propAccessExpr = binExpr.left;
const type = this.tsTypeChecker.getTypeAtLocation(propAccessExpr.expression);
return this.isLibrarySymbol(type.symbol);
}
@@ -1482,12 +1586,16 @@ export class TsUtils {
return false;
}
- public isEsObjectType(typeNode: ts.TypeNode): boolean {
- return ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName) &&
- typeNode.typeName.text === TsUtils.ES_OBJECT;
+ static isEsObjectType(typeNode: ts.TypeNode | undefined): boolean {
+ return (
+ !!typeNode &&
+ ts.isTypeReferenceNode(typeNode) &&
+ ts.isIdentifier(typeNode.typeName) &&
+ typeNode.typeName.text === TsUtils.ES_OBJECT
+ );
}
- public isInsideBlock(node: ts.Node): boolean {
+ static isInsideBlock(node: ts.Node): boolean {
let par = node.parent;
while (par) {
if (ts.isBlock(par)) {
@@ -1498,130 +1606,122 @@ export class TsUtils {
return false;
}
- public isEsObjectPossiblyAllowed(typeRef: ts.TypeReferenceNode): boolean {
+ static isEsObjectPossiblyAllowed(typeRef: ts.TypeReferenceNode): boolean {
return ts.isVariableDeclaration(typeRef.parent);
}
- public isValueAssignableToESObject(node: ts.Node): boolean {
+ isValueAssignableToESObject(node: ts.Node): boolean {
if (ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node)) {
return false;
}
const valueType = this.tsTypeChecker.getTypeAtLocation(node);
- return this.isUnsupportedType(valueType) || this.isAnonymousType(valueType);
+ return TsUtils.isUnsupportedType(valueType) || TsUtils.isAnonymousType(valueType);
}
- public getVariableDeclarationTypeNode(node: ts.Node): ts.TypeNode | undefined {
- let sym = this.trueSymbolAtLocation(node);
+ getVariableDeclarationTypeNode(node: ts.Node): ts.TypeNode | undefined {
+ const sym = this.trueSymbolAtLocation(node);
if (sym === undefined) {
return undefined;
}
- return this.getSymbolDeclarationTypeNode(sym);
+ return TsUtils.getSymbolDeclarationTypeNode(sym);
}
- public getSymbolDeclarationTypeNode(sym: ts.Symbol): ts.TypeNode | undefined {
- const decl = this.getDeclaration(sym);
+ static getSymbolDeclarationTypeNode(sym: ts.Symbol): ts.TypeNode | undefined {
+ const decl = TsUtils.getDeclaration(sym);
if (!!decl && ts.isVariableDeclaration(decl)) {
return decl.type;
}
return undefined;
}
- public hasEsObjectType(node: ts.Node): boolean {
- const typeNode = this.getVariableDeclarationTypeNode(node)
- return typeNode !== undefined && this.isEsObjectType(typeNode);
+ hasEsObjectType(node: ts.Node): boolean {
+ const typeNode = this.getVariableDeclarationTypeNode(node);
+ return typeNode !== undefined && TsUtils.isEsObjectType(typeNode);
}
- public symbolHasEsObjectType(sym: ts.Symbol): boolean {
- const typeNode = this.getSymbolDeclarationTypeNode(sym);
- return typeNode !== undefined && this.isEsObjectType(typeNode);
+ static symbolHasEsObjectType(sym: ts.Symbol): boolean {
+ const typeNode = TsUtils.getSymbolDeclarationTypeNode(sym);
+ return typeNode !== undefined && TsUtils.isEsObjectType(typeNode);
}
- public isEsObjectSymbol(sym: ts.Symbol): boolean {
- let decl = this.getDeclaration(sym);
- return !!decl && ts.isTypeAliasDeclaration(decl) && decl.name.escapedText === TsUtils.ES_OBJECT &&
- decl.type.kind === ts.SyntaxKind.AnyKeyword;
+ static isEsObjectSymbol(sym: ts.Symbol): boolean {
+ const decl = TsUtils.getDeclaration(sym);
+ return (
+ !!decl &&
+ ts.isTypeAliasDeclaration(decl) &&
+ decl.name.escapedText === TsUtils.ES_OBJECT &&
+ decl.type.kind === ts.SyntaxKind.AnyKeyword
+ );
}
- public isAnonymousType(type: ts.Type): boolean {
+ static isAnonymousType(type: ts.Type): boolean {
if (type.isUnionOrIntersection()) {
- for (let compType of type.types) {
- if (this.isAnonymousType(compType)) {
+ for (const compType of type.types) {
+ if (TsUtils.isAnonymousType(compType)) {
return true;
}
}
return false;
}
- return (type.flags & ts.TypeFlags.Object) !== 0 &&
- ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0;
+ return (
+ (type.flags & ts.TypeFlags.Object) !== 0 && ((type as ts.ObjectType).objectFlags & ts.ObjectFlags.Anonymous) !== 0
+ );
}
- public getSymbolOfCallExpression(callExpr: ts.CallExpression): ts.Symbol | undefined {
+ getSymbolOfCallExpression(callExpr: ts.CallExpression): ts.Symbol | undefined {
const signature = this.tsTypeChecker.getResolvedSignature(callExpr);
const signDecl = signature?.getDeclaration();
- if (signDecl && signDecl.name) {
+ if (signDecl?.name) {
return this.tsTypeChecker.getSymbolAtLocation(signDecl.name);
}
return undefined;
}
- // has to be re-implemented with local loop detection
- public typeIsRecursive(topType: ts.Type, type: ts.Type | undefined = undefined): boolean {
- if (type === undefined) {
- type = topType;
- } else if (type === topType) {
- return true;
- } else if (type.aliasSymbol) {
+ isFunctionCalledRecursively(funcExpr: ts.FunctionExpression): boolean {
+ if (!funcExpr.name) {
return false;
}
- if (type.isUnion()) {
- for (let unionElem of type.types) {
- if (this.typeIsRecursive(topType, unionElem)) {
- return true;
- }
- }
- }
- if (type.flags & ts.TypeFlags.Object && (type as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) {
- const typeArgs = this.tsTypeChecker.getTypeArguments(type as ts.TypeReference);
- if (typeArgs) {
- for (const typeArg of typeArgs) {
- if (this.typeIsRecursive(topType, typeArg)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- public isFunctionCalledRecursively(funcExpr: ts.FunctionExpression): boolean {
- if (!funcExpr.name) return false;
-
const sym = this.tsTypeChecker.getSymbolAtLocation(funcExpr.name);
- if (!sym) return false;
+ if (!sym) {
+ return false;
+ }
let found = false;
- const self = this;
- function visitNode(tsNode: ts.Node) {
- // Stop visiting child nodes if finished searching.
- if (found) {
- return;
- }
-
- if (ts.isCallExpression(tsNode) && ts.isIdentifier(tsNode.expression)) {
- const callSym = self.tsTypeChecker.getSymbolAtLocation(tsNode.expression);
+ const callback = (node: ts.Node): void => {
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
+ const callSym = this.tsTypeChecker.getSymbolAtLocation(node.expression);
if (callSym && callSym === sym) {
found = true;
- return;
}
}
+ };
- // Visit children nodes.
- tsNode.forEachChild(visitNode);
- }
+ const stopCondition = (node: ts.Node): boolean => {
+ void node;
+ return found;
+ };
- visitNode(funcExpr);
+ forEachNodeInSubtree(funcExpr, callback, stopCondition);
return found;
}
+
+ getTypeOrTypeConstraintAtLocation(expr: ts.Expression): ts.Type {
+ const type = this.tsTypeChecker.getTypeAtLocation(expr);
+ if (type.isTypeParameter()) {
+ const constraint = type.getConstraint();
+ if (constraint) {
+ return constraint;
+ }
+ }
+ return type;
+ }
+
+ private areCompatibleFunctionals(lhsType: ts.Type, rhsType: ts.Type): boolean {
+ return (
+ (this.isStdFunctionType(lhsType) || TsUtils.isFunctionalType(lhsType)) &&
+ (this.isStdFunctionType(rhsType) || TsUtils.isFunctionalType(rhsType))
+ );
+ }
}
diff --git a/ets2panda/linter/src/utils/functions/identiferUseInValueContext.ts b/ets2panda/linter-4.2/src/identiferUseInValueContext.ts
similarity index 69%
rename from ets2panda/linter/src/utils/functions/identiferUseInValueContext.ts
rename to ets2panda/linter-4.2/src/identiferUseInValueContext.ts
index 43c1389a96b66fc1843d3fc26fc7d0d8415258e1..0ebd1ed973f32db1c527876c1b949b60d9991f8a 100644
--- a/ets2panda/linter/src/utils/functions/identiferUseInValueContext.ts
+++ b/ets2panda/linter-4.2/src/identiferUseInValueContext.ts
@@ -16,22 +16,28 @@
import * as ts from 'typescript';
function isInstanceofContext(tsIdentStart: ts.Node): boolean {
- return ts.isBinaryExpression(tsIdentStart.parent) &&
+ return (
+ ts.isBinaryExpression(tsIdentStart.parent) &&
tsIdentStart.parent.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword
+ );
}
function isNewExpressionContext(tsIdentStart: ts.Node): boolean {
- return ts.isNewExpression(tsIdentStart.parent) && tsIdentStart === tsIdentStart.parent.expression
+ return ts.isNewExpression(tsIdentStart.parent) && tsIdentStart === tsIdentStart.parent.expression;
}
+/*
+ * If identifier is the right-most name of Property Access chain or Qualified name,
+ * or it's a separate identifier expression, then identifier is being referenced as an value.
+ */
function isQualifiedNameContext(tsIdentStart: ts.Node, tsIdentifier: ts.Identifier): boolean {
// rightmost in AST is rightmost in qualified name chain
- return ts.isQualifiedName(tsIdentStart) && tsIdentifier !== tsIdentStart.right
+ return ts.isQualifiedName(tsIdentStart) && tsIdentifier !== tsIdentStart.right;
}
function isPropertyAccessContext(tsIdentStart: ts.Node, tsIdentifier: ts.Identifier): boolean {
// rightmost in AST is rightmost in qualified name chain
- return ts.isPropertyAccessExpression(tsIdentStart) && tsIdentifier !== tsIdentStart.name
+ return ts.isPropertyAccessExpression(tsIdentStart) && tsIdentifier !== tsIdentStart.name;
}
function getQualifiedStart(ident: ts.Node): ts.Node {
@@ -43,27 +49,18 @@ function getQualifiedStart(ident: ts.Node): ts.Node {
}
function isEnumPropAccess(ident: ts.Identifier, tsSym: ts.Symbol, context: ts.Node): boolean {
- return ts.isElementAccessExpression(context) &&
- (context as ts.ElementAccessExpression).expression == ident &&
- !!(tsSym.flags & ts.SymbolFlags.Enum);
-}
-
-function isValidTypeNode(node: ts.TypeNode): boolean {
- return !ts.isTypeOfExpression(node);
+ return (
+ ts.isElementAccessExpression(context) &&
+ !!(tsSym.flags & ts.SymbolFlags.Enum) &&
+ (context.expression === ident ||
+ ts.isPropertyAccessExpression(context.expression) && context.expression.name === ident)
+ );
}
-export function identiferUseInValueContext(
- ident: ts.Identifier, tsSym: ts.Symbol
-) {
- let qualifiedStart = getQualifiedStart(ident);
- let parent = qualifiedStart.parent;
-
- return !(
- // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery)
- ts.isTypeNode(parent) && isValidTypeNode(parent) ||
- // If identifier is the right-most name of Property Access chain or Qualified name,
- // or it's a separate identifier expression, then identifier is being referenced as an value.
- isEnumPropAccess(ident, tsSym, parent) ||
+function isValidParent(parent: ts.Node): boolean {
+ // treat TypeQuery as valid because it's already forbidden (FaultID.TypeQuery)
+ return (
+ ts.isTypeNode(parent) && !ts.isTypeOfExpression(parent) ||
ts.isExpressionWithTypeArguments(parent) ||
ts.isExportAssignment(parent) ||
ts.isExportSpecifier(parent) ||
@@ -75,10 +72,19 @@ export function identiferUseInValueContext(
ts.isEnumDeclaration(parent) ||
ts.isNamespaceImport(parent) ||
ts.isImportSpecifier(parent) ||
+ ts.isImportEqualsDeclaration(parent)
+ );
+}
+
+export function identiferUseInValueContext(ident: ts.Identifier, tsSym: ts.Symbol): boolean {
+ const qualifiedStart = getQualifiedStart(ident);
+ const parent = qualifiedStart.parent;
+ const isValidUse =
+ isValidParent(parent) ||
+ isEnumPropAccess(ident, tsSym, parent) ||
isQualifiedNameContext(qualifiedStart, ident) ||
isPropertyAccessContext(qualifiedStart, ident) ||
isNewExpressionContext(qualifiedStart) ||
- isInstanceofContext(qualifiedStart) ||
- ts.isImportEqualsDeclaration(parent)
- );
+ isInstanceofContext(qualifiedStart);
+ return !isValidUse;
}
diff --git a/ets2panda/linter-4.2/src/ts-diagnostics/TSCCompiledProgram.ts b/ets2panda/linter-4.2/src/ts-diagnostics/TSCCompiledProgram.ts
index 29548ef9d8c38d5d0772d0a9dd1691ccc4b02a46..ef320535f95bbe088d8eb9c7511d596e34bac43e 100644
--- a/ets2panda/linter-4.2/src/ts-diagnostics/TSCCompiledProgram.ts
+++ b/ets2panda/linter-4.2/src/ts-diagnostics/TSCCompiledProgram.ts
@@ -14,17 +14,17 @@
*/
import * as ts from 'typescript';
-import { ProblemInfo } from '../ProblemInfo';
+import type { ProblemInfo } from '../ProblemInfo';
import { ProblemSeverity } from '../ProblemSeverity';
-import { LintOptions } from '../LintOptions';
+import type { LintOptions } from '../LintOptions';
import { TypeScriptDiagnosticsExtractor } from './TypeScriptDiagnosticsExtractor';
import { compile } from '../CompilerWrapper';
import { getNodeOrLineEnd } from '../Utils';
import { FaultID, faultsAttrs } from '../Problems';
export class TSCCompiledProgram {
- private diagnosticsExtractor: TypeScriptDiagnosticsExtractor;
- private wasStrict: boolean;
+ private readonly diagnosticsExtractor: TypeScriptDiagnosticsExtractor;
+ private readonly wasStrict: boolean;
constructor(program: ts.Program, options: LintOptions) {
const { strict, nonStrict, wasStrict } = getTwoCompiledVersions(program, options);
@@ -32,54 +32,61 @@ export class TSCCompiledProgram {
this.wasStrict = wasStrict;
}
- public getOriginalProgram(): ts.Program {
- return this.wasStrict
- ? this.diagnosticsExtractor.strictProgram
- : this.diagnosticsExtractor.nonStrictProgram;
+ getOriginalProgram(): ts.Program {
+ return this.wasStrict ? this.diagnosticsExtractor.strictProgram : this.diagnosticsExtractor.nonStrictProgram;
}
- public getStrictDiagnostics(fileName: string): ts.Diagnostic[] {
+ getStrictDiagnostics(fileName: string): ts.Diagnostic[] {
return this.diagnosticsExtractor.getStrictDiagnostics(fileName);
}
}
-export function getStrictOptions(strict: boolean = true) {
+export function getStrictOptions(): {
+ strictNullChecks: boolean;
+ strictFunctionTypes: boolean;
+ strictPropertyInitialization: boolean;
+ noImplicitReturns: boolean;
+} {
return {
- strictNullChecks: strict,
- strictFunctionTypes: strict,
- strictPropertyInitialization: strict,
- noImplicitReturns: strict,
- }
+ strictNullChecks: true,
+ strictFunctionTypes: true,
+ strictPropertyInitialization: true,
+ noImplicitReturns: true
+ };
+}
+
+function isStrict(compilerOptions: ts.CompilerOptions): boolean {
+ const strictOptions = getStrictOptions();
+ let wasStrict = false;
+ // wasStrict evaluates true if any of the strict options was set
+ Object.keys(strictOptions).forEach((x) => {
+ wasStrict = wasStrict || !!compilerOptions[x];
+ });
+ return wasStrict;
}
function getTwoCompiledVersions(
program: ts.Program,
- options: LintOptions,
+ options: LintOptions
): { strict: ts.Program; nonStrict: ts.Program; wasStrict: boolean } {
- const compilerOptions = { ...program.getCompilerOptions()};
-
- const wasStrict = inverseStrictOptions(compilerOptions);
- const inversedOptions = getStrictOptions(!wasStrict);
+ const compilerOptions = program.getCompilerOptions();
+ const inversedOptions = getInversedOptions(compilerOptions);
const withInversedOptions = compile(options, inversedOptions);
-
+ const wasStrict = isStrict(compilerOptions);
return {
strict: wasStrict ? program : withInversedOptions,
nonStrict: wasStrict ? withInversedOptions : program,
- wasStrict: wasStrict,
- }
+ 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];
+function getInversedOptions(compilerOptions: ts.CompilerOptions): ts.CompilerOptions {
+ const newOptions = { ...compilerOptions };
+ const wasStrict = isStrict(compilerOptions);
+ Object.keys(getStrictOptions()).forEach((key) => {
+ newOptions[key] = !wasStrict;
});
- // wasStrict evaluates true if any of the strict options was set
- return wasStrict;
+ return newOptions;
}
export function transformDiagnostic(diagnostic: ts.Diagnostic): ProblemInfo {
@@ -96,12 +103,12 @@ export function transformDiagnostic(diagnostic: ts.Diagnostic): ProblemInfo {
start: startPos,
end: endPos,
type: 'StrictModeError',
- severity: ProblemSeverity.ERROR, // expect strict options to always present
+ severity: ProblemSeverity.ERROR,
problem: FaultID[faultId],
suggest: messageText,
rule: messageText,
ruleTag: faultsAttrs[faultId] ? Number(faultsAttrs[faultId].cookBookRef) : 0,
- autofixable: false,
+ autofixable: false
};
}
@@ -109,10 +116,10 @@ export function transformDiagnostic(diagnostic: ts.Diagnostic): ProblemInfo {
* 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!);
+ const { line, character } = diagnostic.file!.getLineAndCharacterOfPosition(diagnostic.start!);
// TSC counts lines and columns from zero
return {
line: line + 1,
- column: character + 1,
- }
+ column: character + 1
+ };
}
diff --git a/ets2panda/linter-4.2/src/ts-diagnostics/TypeScriptDiagnosticsExtractor.ts b/ets2panda/linter-4.2/src/ts-diagnostics/TypeScriptDiagnosticsExtractor.ts
index 0d5106dd9f116531b356ca406fc9f4fff8ef053a..7ce161575f759311a4bfc550c1ac81c0ff97c1ef 100644
--- a/ets2panda/linter-4.2/src/ts-diagnostics/TypeScriptDiagnosticsExtractor.ts
+++ b/ets2panda/linter-4.2/src/ts-diagnostics/TypeScriptDiagnosticsExtractor.ts
@@ -13,19 +13,22 @@
* limitations under the License.
*/
-import * as ts from 'typescript';
+import type * as ts from 'typescript';
export class TypeScriptDiagnosticsExtractor {
- constructor(public strictProgram: ts.Program, public nonStrictProgram: ts.Program) {
- }
+ constructor(
+ public strictProgram: ts.Program,
+ public nonStrictProgram: ts.Program
+ ) {}
/**
* Returns diagnostics which appear in strict compilation mode only
*/
- public getStrictDiagnostics(fileName: string): ts.Diagnostic[] {
+ getStrictDiagnostics(fileName: string): ts.Diagnostic[] {
// applying filter is a workaround for tsc bug
- const strict = getAllDiagnostics(this.strictProgram, fileName)
- .filter(diag => !(diag.length === 0 && diag.start === 0));
+ const strict = getAllDiagnostics(this.strictProgram, fileName).filter((diag) => {
+ return !(diag.length === 0 && diag.start === 0);
+ });
const nonStrict = getAllDiagnostics(this.nonStrictProgram, fileName);
// collect hashes for later easier comparison
@@ -37,18 +40,21 @@ export class TypeScriptDiagnosticsExtractor {
return result;
}, new Set());
// return diagnostics which weren't detected in non-strict mode
- return strict.filter(value => {
+ return strict.filter((value) => {
const hash = hashDiagnostic(value);
- return (hash && !nonStrictHashes.has(hash));
+ return hash && !nonStrictHashes.has(hash);
});
}
}
function getAllDiagnostics(program: ts.Program, fileName: string): ts.Diagnostic[] {
const sourceFile = program.getSourceFile(fileName);
- return program.getSemanticDiagnostics(sourceFile)
- .concat(program.getSyntacticDiagnostics(sourceFile))
- .filter(diag => diag.file === sourceFile);
+ return program.
+ getSemanticDiagnostics(sourceFile).
+ concat(program.getSyntacticDiagnostics(sourceFile)).
+ filter((diag) => {
+ return diag.file === sourceFile;
+ });
}
function hashDiagnostic(diagnostic: ts.Diagnostic): string | undefined {
diff --git a/ets2panda/linter-4.2/stats_calculator/src/calculate-stats.ts b/ets2panda/linter-4.2/stats_calculator/src/calculate-stats.ts
index bd0f5a4bff858416ea0c5fb1e29814117b7d11b6..3f8eb2410e7e586dc36918afd67bad9233abad6b 100644
--- a/ets2panda/linter-4.2/stats_calculator/src/calculate-stats.ts
+++ b/ets2panda/linter-4.2/stats_calculator/src/calculate-stats.ts
@@ -44,7 +44,7 @@ interface ArkTsIssueType {
description: string;
type: string;
count: number;
-};
+}
interface Statistics {
totalErrors: number;
@@ -52,22 +52,22 @@ interface Statistics {
linesWithErrors: number;
linesWithWarnings: number;
issues: Map;
-};
+}
function isError(defectInfo: DefectInfo): boolean {
return defectInfo.category === ARK_TS_ISSUES_ERROR_CATEGORY;
}
-function fillIssueInfo(statistics: Statistics, defectInfo: DefectInfo) {
- const recipeNo = parseInt(defectInfo.ruleDocPath!!.substring(
- 'docs/recipe'.length,
- defectInfo.ruleDocPath!!.length - '.md'.length));
- let issueInfo = statistics.issues.get(recipeNo);
+function fillIssueInfo(statistics: Statistics, defectInfo: DefectInfo): void {
+ const recipeNo = parseInt(
+ defectInfo.ruleDocPath.substring('docs/recipe'.length, defectInfo.ruleDocPath.length - '.md'.length)
+ );
+ const issueInfo = statistics.issues.get(recipeNo);
if (!issueInfo) {
statistics.issues.set(recipeNo, {
description: defectInfo.description,
type: isError(defectInfo) ? 'error' : 'warn',
- count: 1,
+ count: 1
});
} else {
issueInfo.count += 1;
@@ -75,7 +75,7 @@ function fillIssueInfo(statistics: Statistics, defectInfo: DefectInfo) {
}
function parse(reportJson: ReportJson): Statistics {
- let statistics: Statistics = {
+ const statistics: Statistics = {
totalErrors: 0,
totalWarnings: 0,
linesWithErrors: 0,
@@ -93,7 +93,7 @@ function parse(reportJson: ReportJson): Statistics {
continue;
}
- fillIssueInfo(statistics, defectInfo)
+ fillIssueInfo(statistics, defectInfo);
if (isError(defectInfo)) {
statistics.totalErrors += 1;
@@ -114,7 +114,7 @@ function read(filePath: string): ReportJson {
return JSON.parse(fs.readFileSync(filePath, { encoding: 'utf8', flag: 'r' }));
}
-function main() {
+function main(): void {
if (process.argv.length < 3) {
console.error('Path to input json was not provided, exiting');
process.exit(1);
@@ -122,6 +122,8 @@ function main() {
console.log(parse(read(process.argv[2])));
}
-// file is stored in project's directory under the following path:
-// /.idea/code-linter/eslintAgent/output.json
-main()
+/*
+ * file is stored in project's directory under the following path:
+ * /.idea/code-linter/eslintAgent/output.json
+ */
+main();
diff --git a/ets2panda/linter-4.2/test/ambient_module.ts.relax.json b/ets2panda/linter-4.2/test/ambient_module.ts.relax.json
old mode 100644
new mode 100755
index a02ca7fc8935464e83947500d0dfc1e6558752dd..81f9bd90b4a34906dbf4f322ce7315bf6de3de12
--- a/ets2panda/linter-4.2/test/ambient_module.ts.relax.json
+++ b/ets2panda/linter-4.2/test/ambient_module.ts.relax.json
@@ -26,7 +26,7 @@
"column": 3,
"problem": "NonDeclarationInNamespace",
"suggest": "",
- "rule": "Non-declaration statements in namespaces are not supported (arkts-no-ns-statements)"
+ "rule": "Non-declaration statements in namespaces are not supported (single semicolons are considered as empty non-declaration statements) (arkts-no-ns-statements)"
},
{
"line": 20,
@@ -47,7 +47,7 @@
"column": 3,
"problem": "NonDeclarationInNamespace",
"suggest": "",
- "rule": "Non-declaration statements in namespaces are not supported (arkts-no-ns-statements)"
+ "rule": "Non-declaration statements in namespaces are not supported (single semicolons are considered as empty non-declaration statements) (arkts-no-ns-statements)"
},
{
"line": 25,
diff --git a/ets2panda/linter-4.2/test/ambient_module.ts.strict.json b/ets2panda/linter-4.2/test/ambient_module.ts.strict.json
old mode 100644
new mode 100755
index a02ca7fc8935464e83947500d0dfc1e6558752dd..81f9bd90b4a34906dbf4f322ce7315bf6de3de12
--- a/ets2panda/linter-4.2/test/ambient_module.ts.strict.json
+++ b/ets2panda/linter-4.2/test/ambient_module.ts.strict.json
@@ -26,7 +26,7 @@
"column": 3,
"problem": "NonDeclarationInNamespace",
"suggest": "",
- "rule": "Non-declaration statements in namespaces are not supported (arkts-no-ns-statements)"
+ "rule": "Non-declaration statements in namespaces are not supported (single semicolons are considered as empty non-declaration statements) (arkts-no-ns-statements)"
},
{
"line": 20,
@@ -47,7 +47,7 @@
"column": 3,
"problem": "NonDeclarationInNamespace",
"suggest": "",
- "rule": "Non-declaration statements in namespaces are not supported (arkts-no-ns-statements)"
+ "rule": "Non-declaration statements in namespaces are not supported (single semicolons are considered as empty non-declaration statements) (arkts-no-ns-statements)"
},
{
"line": 25,
diff --git a/ets2panda/linter-4.2/test/classB.ts.relax.json b/ets2panda/linter-4.2/test/classB.ts.relax.json
index e44fd80c47fb55cd43e0a82e2db3b2b8cf8c8858..dc2b9ced44f0b0fed43aafdf3a49426c859daf07 100644
--- a/ets2panda/linter-4.2/test/classB.ts.relax.json
+++ b/ets2panda/linter-4.2/test/classB.ts.relax.json
@@ -15,8 +15,8 @@
],
"nodes": [
{
- "line": 24,
- "column": 5,
+ "line": 25,
+ "column": 16,
"problem": "FunctionContainsThis",
"suggest": "",
"rule": "Using \"this\" inside stand-alone functions is not supported (arkts-no-standalone-this)"
diff --git a/ets2panda/linter-4.2/test/classB.ts.strict.json b/ets2panda/linter-4.2/test/classB.ts.strict.json
index e44fd80c47fb55cd43e0a82e2db3b2b8cf8c8858..dc2b9ced44f0b0fed43aafdf3a49426c859daf07 100644
--- a/ets2panda/linter-4.2/test/classB.ts.strict.json
+++ b/ets2panda/linter-4.2/test/classB.ts.strict.json
@@ -15,8 +15,8 @@
],
"nodes": [
{
- "line": 24,
- "column": 5,
+ "line": 25,
+ "column": 16,
"problem": "FunctionContainsThis",
"suggest": "",
"rule": "Using \"this\" inside stand-alone functions is not supported (arkts-no-standalone-this)"
diff --git a/ets2panda/linter-4.2/test/class_as_object.ts b/ets2panda/linter-4.2/test/class_as_object.ts
index 302432975cbb76c7c446fa410aad9b6220153f5d..1ee2fb6e857889da224b5f4618da118e29d4072c 100644
--- a/ets2panda/linter-4.2/test/class_as_object.ts
+++ b/ets2panda/linter-4.2/test/class_as_object.ts
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-import { Something, SomethingFactory, SomethingBar, Bar } from "./oh_modules/ohos_factory";
+import { Something, SomethingFactory, SomethingBar, Bar, Select } from "./oh_modules/ohos_factory";
class C {
static a = 5;
@@ -91,3 +91,33 @@ for (let item = 0; item < Object.keys(Color).length; item++) {
foo2(() => C);
export { C as H };
+
+// #14228
+let data = new Select