Port no import default of export equals (#657)

* Port no-import-default-of-export-equals

First draft and 2/3 of the original tests. The test of modules, the one
that matters, doesn't work with typescript-eslint's test infrastructure
though.

* Port no-import-default-of-export-equals

Also update TS to 5.0 and Jest to 29.

Associated minor changes to jest config (no more top-level ts-node) and
some tslint rules (switch to a combined decorator/modifier list, but
only on select nodes).

* yarn format

* Fix lint and update new jest baseline format

More like real JSON!

* also update dts-critic (??)
This commit is contained in:
Nathan Shively-Sanders
2023-03-27 10:00:11 -07:00
committed by GitHub
parent cb0d73d177
commit eba074f006
25 changed files with 953 additions and 1820 deletions

View File

@@ -3,10 +3,10 @@ module.exports = {
testEnvironment: "node",
modulePathIgnorePatterns: ["packages\\/publisher\\/output"],
testMatch: ["<rootDir>/packages/*/test/**/*.test.ts", "<rootDir>/packages/dts-critic/index.test.ts"],
globals: {
"ts-jest": {
tsConfig: "<rootDir>/tsconfig.test.json",
transform: {
"^.+\\.tsx?$": ["ts-jest", {
tsconfig: "<rootDir>/tsconfig.test.json",
diagnostics: false
}
}]
}
};

View File

@@ -14,7 +14,7 @@
"retag": "node packages/retag/dist/retag.js"
},
"devDependencies": {
"@types/jest": "^25.1.3",
"@types/jest": "^29.5.0",
"@types/node": "^14.14.37",
"@types/yargs": "^15.0.4",
"@typescript-eslint/eslint-plugin": "^5.55.0",
@@ -22,12 +22,12 @@
"eslint": "^7.31.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsdoc": "^30.7.8",
"jest": "^25.1.0",
"jest": "^29.5.0",
"lerna": "^4.0.0",
"prettier": "^2.6.2",
"ts-jest": "^25.2.1",
"ts-jest": "^29.0.5",
"tslint": "^6.1.2",
"tslint-microsoft-contrib": "^6.2.0",
"typescript": "4.7.4"
"typescript": "^5.0.2"
}
}

View File

@@ -829,8 +829,8 @@ function isExportConstruct(node: ts.Node): boolean {
}
function hasExportModifier(node: ts.Node): boolean {
if (node.modifiers) {
return node.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
if (ts.canHaveModifiers(node)) {
return !!ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
}
return false;
}

View File

@@ -39,14 +39,14 @@
"yargs": "^15.1.0"
},
"peerDependencies": {
"typescript": ">= 3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.7.0-dev || >= 3.8.0-dev || >= 3.9.0-dev || >= 4.0.0-dev"
"typescript": ">= 3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.7.0-dev || >= 3.8.0-dev || >= 3.9.0-dev || >= 4.0.0-dev || >=5.0.0-dev"
},
"devDependencies": {
"@types/eslint": "^8.4.2",
"@types/fs-extra": "^5.0.2",
"@types/json-stable-stringify": "^1.0.32",
"@types/strip-json-comments": "^0.0.28",
"typescript": "4.7.4"
"typescript": "^5.0.2"
},
"engines": {
"node": ">=10.0.0"

View File

@@ -0,0 +1,62 @@
import { createRule } from "../util";
import { ESLintUtils } from "@typescript-eslint/utils";
import * as ts from "typescript";
const rule = createRule({
name: "no-import-default-of-export-equals",
defaultOptions: [],
meta: {
type: "problem",
docs: {
description: "Forbid a default import to reference an `export =` module.",
recommended: "error",
},
messages: {
noImportDefaultOfExportEquals: `The module {{moduleName}} uses \`export = \`. Import with \`import {{importName}} = require({{moduleName}})\`.`,
},
schema: [],
},
create(context) {
const parserServices = ESLintUtils.getParserServices(context);
const checker = parserServices.program.getTypeChecker();
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
ImportDeclaration(node) {
const defaultName = node.specifiers.find((spec) => spec.type === "ImportDefaultSpecifier")?.local;
if (!defaultName) {
return;
}
const importName = defaultName.name;
const source = parserServices.esTreeNodeToTSNodeMap.get(node.source);
const sym = checker.getSymbolAtLocation(source);
if (
sym?.declarations?.some((d) => getStatements(d)?.some((s) => ts.isExportAssignment(s) && !!s.isExportEquals))
) {
context.report({
messageId: "noImportDefaultOfExportEquals",
data: { moduleName: node.source, importName },
node: defaultName,
});
}
},
};
},
});
function getStatements(decl: ts.Declaration): readonly ts.Statement[] | undefined {
return ts.isSourceFile(decl)
? decl.statements
: ts.isModuleDeclaration(decl)
? getModuleDeclarationStatements(decl)
: undefined;
}
function getModuleDeclarationStatements(node: ts.ModuleDeclaration): readonly ts.Statement[] | undefined {
let { body } = node;
while (body && body.kind === ts.SyntaxKind.ModuleDeclaration) {
body = body.body;
}
return body && ts.isModuleBlock(body) ? body.statements : undefined;
}
export = rule;

View File

@@ -1,58 +0,0 @@
import * as Lint from "tslint";
import * as ts from "typescript";
import { eachModuleStatement, failure, getModuleDeclarationStatements } from "../util";
export class Rule extends Lint.Rules.TypedRule {
static metadata: Lint.IRuleMetadata = {
ruleName: "no-import-default-of-export-equals",
description: "Forbid a default import to reference an `export =` module.",
optionsDescription: "Not configurable.",
options: null,
type: "functionality",
typescriptOnly: true,
};
// eslint-disable-next-line @typescript-eslint/naming-convention
static FAILURE_STRING(importName: string, moduleName: string): string {
return failure(
Rule.metadata.ruleName,
`The module ${moduleName} uses \`export = \`. Import with \`import ${importName} = require(${moduleName})\`.`
);
}
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, (ctx) => walk(ctx, program.getTypeChecker()));
}
}
function walk(ctx: Lint.WalkContext<void>, checker: ts.TypeChecker): void {
eachModuleStatement(ctx.sourceFile, (statement) => {
if (!ts.isImportDeclaration(statement)) {
return;
}
const defaultName = statement.importClause && statement.importClause.name;
if (!defaultName) {
return;
}
const sym = checker.getSymbolAtLocation(statement.moduleSpecifier);
if (
sym &&
sym.declarations &&
sym.declarations.some((d) => {
const statements = getStatements(d);
return statements !== undefined && statements.some((s) => ts.isExportAssignment(s) && !!s.isExportEquals);
})
) {
ctx.addFailureAtNode(defaultName, Rule.FAILURE_STRING(defaultName.text, statement.moduleSpecifier.getText()));
}
});
}
function getStatements(decl: ts.Declaration): readonly ts.Statement[] | undefined {
return ts.isSourceFile(decl)
? decl.statements
: ts.isModuleDeclaration(decl)
? getModuleDeclarationStatements(decl)
: undefined;
}

View File

@@ -222,7 +222,8 @@ function removeTypeExpression(
function isInAmbientContext(node: ts.Node): boolean {
return ts.isSourceFile(node)
? node.isDeclarationFile
: Lint.hasModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword) || isInAmbientContext(node.parent!);
: (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword)) ||
isInAmbientContext(node.parent!);
}
const redundantTags = new Set([

View File

@@ -54,7 +54,7 @@ function walk(ctx: Lint.WalkContext<void>): void {
// `declare global` and `declare module "foo"` OK. `declare namespace N` not OK, should be `export namespace`.
if (!isDeclareGlobalOrExternalModuleDeclaration(node)) {
if (isDeclare(node)) {
fail(mod(node, ts.SyntaxKind.DeclareKeyword), "'declare' keyword is redundant here.");
fail(mod(node, ts.SyntaxKind.DeclareKeyword)!, "'declare' keyword is redundant here.");
}
if (autoExportEnabled && !isExport(node)) {
fail(
@@ -76,7 +76,7 @@ function walk(ctx: Lint.WalkContext<void>): void {
node.kind === ts.SyntaxKind.InterfaceDeclaration ||
node.kind === ts.SyntaxKind.TypeAliasDeclaration
) {
fail(mod(node, ts.SyntaxKind.DeclareKeyword), "'declare' keyword is redundant here.");
fail(mod(node, ts.SyntaxKind.DeclareKeyword)!, "'declare' keyword is redundant here.");
}
}
}
@@ -85,8 +85,8 @@ function walk(ctx: Lint.WalkContext<void>): void {
ctx.addFailureAtNode(node, failure(Rule.metadata.ruleName, reason));
}
function mod(node: ts.Statement, kind: ts.SyntaxKind): ts.Node {
return node.modifiers!.find((m) => m.kind === kind)!;
function mod(node: ts.Statement, kind: ts.SyntaxKind): ts.Node | undefined {
return ts.canHaveModifiers(node) ? ts.getModifiers(node)?.find((m) => m.kind === kind) : undefined;
}
function checkModule(moduleDeclaration: ts.ModuleDeclaration): void {
@@ -110,7 +110,7 @@ function walk(ctx: Lint.WalkContext<void>): void {
// Compiler will error for 'declare' here anyway, so just check for 'export'.
if (isExport(s) && autoExportEnabled && !isDefault(s)) {
fail(
mod(s, ts.SyntaxKind.ExportKeyword),
mod(s, ts.SyntaxKind.ExportKeyword)!,
"'export' keyword is redundant here because " +
"all declarations in this module are exported automatically. " +
"If you have a good reason to export some declarations and not others, " +
@@ -138,15 +138,15 @@ function isModuleDeclaration(node: ts.Node): node is ts.ModuleDeclaration {
}
function isDeclare(node: ts.Node): boolean {
return Lint.hasModifier(node.modifiers, ts.SyntaxKind.DeclareKeyword);
return ts.canHaveModifiers(node) && !!ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword);
}
function isExport(node: ts.Node): boolean {
return Lint.hasModifier(node.modifiers, ts.SyntaxKind.ExportKeyword);
return ts.canHaveModifiers(node) && !!ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
}
function isDefault(node: ts.Node): boolean {
return Lint.hasModifier(node.modifiers, ts.SyntaxKind.DefaultKeyword);
return ts.canHaveModifiers(node) && !!ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword);
}
// tslint:disable-next-line:max-line-length

View File

@@ -31,33 +31,6 @@ export function getCommonDirectoryName(files: readonly string[]): string {
return basename(minDir);
}
export function eachModuleStatement(sourceFile: ts.SourceFile, action: (statement: ts.Statement) => void): void {
if (!sourceFile.isDeclarationFile) {
return;
}
for (const node of sourceFile.statements) {
if (ts.isModuleDeclaration(node)) {
const statements = getModuleDeclarationStatements(node);
if (statements) {
for (const statement of statements) {
action(statement);
}
}
} else {
action(node);
}
}
}
export function getModuleDeclarationStatements(node: ts.ModuleDeclaration): readonly ts.Statement[] | undefined {
let { body } = node;
while (body && body.kind === ts.SyntaxKind.ModuleDeclaration) {
body = body.body;
}
return body && ts.isModuleBlock(body) ? body.statements : undefined;
}
export async function getCompilerOptions(dirPath: string): Promise<ts.CompilerOptions> {
const tsconfigPath = join(dirPath, "tsconfig.json");
if (!(await pathExists(tsconfigPath))) {

View File

@@ -0,0 +1,48 @@
import { ESLintUtils } from "@typescript-eslint/utils";
import * as noImportDefaultOfExportEquals from "../src/rules/no-import-default-of-export-equals";
const ruleTester = new ESLintUtils.RuleTester({
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2018,
tsconfigRootDir: __dirname,
project: "./tsconfig.no-import-default-of-export-equals.json",
},
});
ruleTester.run("no-import-default-of-export-equals", noImportDefaultOfExportEquals, {
invalid: [
{
filename: "index.d.ts",
code: `declare module "a" {
interface I {}
export = I;
}
declare module "b" {
import a from "a";
}`,
errors: [
{
line: 7,
messageId: "noImportDefaultOfExportEquals",
},
],
},
],
valid: [
{
filename: "index.d.ts",
code: `declare module "a" {
interface I {}
export default I;
}
declare module "b" {
import a from "a";
}
`,
},
],
});

View File

@@ -1,11 +0,0 @@
declare module "a" {
interface I {}
export = I;
}
declare module "b" {
import a from "a";
~ [0]
}
[0]: The module "a" uses `export = `. Import with `import a = require("a")`. See: https://github.com/microsoft/DefinitelyTyped-tools/blob/master/packages/dtslint/docs/no-import-default-of-export-equals.md

View File

@@ -1,6 +0,0 @@
{
"rulesDirectory": ["../../../dist/rules"],
"rules": {
"no-import-default-of-export-equals": true
}
}

View File

@@ -1,2 +0,0 @@
declare function foo(): void;
export = foo;

View File

@@ -1,4 +0,0 @@
import D from "./a";
~ [0]
[0]: The module "./a" uses `export = `. Import with `import D = require("./a")`. See: https://github.com/microsoft/DefinitelyTyped-tools/blob/master/packages/dtslint/docs/no-import-default-of-export-equals.md

View File

@@ -1,7 +0,0 @@
{
"rulesDirectory": ["../../../dist/rules"],
"rules": {
"no-import-default-of-export-equals": true
}
}

View File

@@ -1,9 +0,0 @@
declare module "a" {
interface I {}
export default I;
}
declare module "b" {
import a from "a";
}

View File

@@ -1,6 +0,0 @@
{
"rulesDirectory": ["../../../dist/rules"],
"rules": {
"no-import-default-of-export-equals": true
}
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"skipLibCheck": true,
"strict": true,
"target": "esnext"
},
"files": [
"index.d.ts",
"file.ts"
]
}

View File

@@ -24,7 +24,7 @@
"@types/node": "^12.12.29",
"markdown-table": "^1.1.3",
"semver": "^7.3.7",
"typescript": "^4.1.0",
"typescript": "^5.0.2",
"yargs": "^15.4.1"
},
"publishConfig": {

View File

@@ -1,17 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`analysis summarizePackageBenchmarks snapshot 1`] = `
Object {
{
"batchRunStart": "2019-06-05T21:02:57.283Z",
"benchmarkDuration": 6146.653569,
"completions": Object {
"completions": {
"mean": 148.38288194,
"meanCoefficientOfVariation": 0.23178183452633877,
"median": 146.9316702,
"standardDeviation": 14.526224099473964,
"trials": 20,
"worst": Object {
"completionsDurations": Array [
"worst": {
"completionsDurations": [
260.302261,
161.823348,
209.062858,
@@ -23,7 +23,7 @@ Object {
"identifierText": "abbrev",
"line": 12,
"offset": 15,
"quickInfoDurations": Array [
"quickInfoDurations": [
144.181488,
128.053511,
181.161561,
@@ -38,14 +38,14 @@ Object {
"packageName": "abbrev",
"packageVersionMajor": 1,
"packageVersionMinor": 1,
"quickInfo": Object {
"quickInfo": {
"mean": 140.33250924,
"meanCoefficientOfVariation": 0.21107195517210994,
"median": 139.3310676,
"standardDeviation": 12.553366997435981,
"trials": 20,
"worst": Object {
"completionsDurations": Array [
"worst": {
"completionsDurations": [
188.397316,
111.76699,
154.360542,
@@ -57,7 +57,7 @@ Object {
"identifierText": "ReadonlyArray",
"line": 11,
"offset": 14,
"quickInfoDurations": Array [
"quickInfoDurations": [
209.811926,
229.587525,
166.220571,
@@ -67,7 +67,7 @@ Object {
"start": 262,
},
},
"relationCacheSizes": Object {
"relationCacheSizes": {
"assignable": 104,
"identity": 0,
"subtype": 0,

View File

@@ -23,7 +23,7 @@
"pacote": "^13.6.1",
"semver": "^7.3.7",
"source-map-support": "^0.4.0",
"typescript": "^4.1.0",
"typescript": "^5.0.2",
"yargs": "15.3.1"
},
"devDependencies": {

2452
yarn.lock

File diff suppressed because it is too large Load Diff