forked from cartant/eslint-plugin-rxjs
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: remove tsutils and tsutils-etc (#3)
- Remove unmaintained `tsutils` and `tsutils-etc`. - `tsutils` has been replaced by the community with `ts-api-utils`. See ajafff/tsutils#145. Replacing it was fairly simple. - `tsutils-etc` relies on `tsutils`, and I haven't seen a community-chosen replacement, so we're re-implementing the utilities into this repo, similar to how we re-implemented `eslint-etc` utilities. So utilities now exist in `ts-api-utils`, but `couldBeType` and `couldBeFunction` had to be ported over. - Pull in a dev dependency on `@typescript/vfs` to unit test `couldBeType` based on the `tsutils-etc` unit tests for that function. - Bump the minimum versions of TypeScript to >=4.2.0 to align with `ts-api-utils` and `tslib` to ^2.1.0 to align with `rxjs`.
- Loading branch information
1 parent
804812c
commit c64f9f6
Showing
10 changed files
with
342 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import * as ts from 'typescript'; | ||
import { couldBeType } from './could-be-type'; | ||
|
||
export function couldBeFunction(type: ts.Type): boolean { | ||
return ( | ||
type.getCallSignatures().length > 0 | ||
|| couldBeType(type, 'Function') | ||
|| couldBeType(type, 'ArrowFunction') | ||
|| couldBeType(type, ts.InternalSymbolName.Function) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import * as tsutils from 'ts-api-utils'; | ||
import * as ts from 'typescript'; | ||
|
||
export function couldBeType( | ||
type: ts.Type, | ||
name: string | RegExp, | ||
qualified?: { | ||
name: RegExp; | ||
typeChecker: ts.TypeChecker; | ||
}, | ||
): boolean { | ||
if (tsutils.isTypeReference(type)) { | ||
type = type.target; | ||
} | ||
|
||
if (isType(type, name, qualified)) { | ||
return true; | ||
} | ||
|
||
if (tsutils.isUnionOrIntersectionType(type)) { | ||
return type.types.some(t => couldBeType(t, name, qualified)); | ||
} | ||
|
||
const baseTypes = type.getBaseTypes(); | ||
if (baseTypes?.some(t => couldBeType(t, name, qualified))) { | ||
return true; | ||
} | ||
|
||
if (couldImplement(type, name, qualified)) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function isType( | ||
type: ts.Type, | ||
name: string | RegExp, | ||
qualified?: { | ||
name: RegExp; | ||
typeChecker: ts.TypeChecker; | ||
}, | ||
): boolean { | ||
if (!type.symbol) { | ||
return false; | ||
} | ||
if ( | ||
qualified | ||
&& !qualified.name.test( | ||
qualified.typeChecker.getFullyQualifiedName(type.symbol), | ||
) | ||
) { | ||
return false; | ||
} | ||
return typeof name === 'string' | ||
? type.symbol.name === name | ||
: Boolean(type.symbol.name.match(name)); | ||
} | ||
|
||
function couldImplement( | ||
type: ts.Type, | ||
name: string | RegExp, | ||
qualified?: { | ||
name: RegExp; | ||
typeChecker: ts.TypeChecker; | ||
}, | ||
): boolean { | ||
const { symbol } = type; | ||
if (symbol) { | ||
const { valueDeclaration } = symbol; | ||
if (valueDeclaration && ts.isClassDeclaration(valueDeclaration)) { | ||
const { heritageClauses } = valueDeclaration; | ||
if (heritageClauses) { | ||
const implemented = heritageClauses.some( | ||
({ token, types }) => | ||
token === ts.SyntaxKind.ImplementsKeyword | ||
&& types.some(node => isMatchingNode(node, name, qualified)), | ||
); | ||
if (implemented) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function isMatchingNode( | ||
node: ts.ExpressionWithTypeArguments, | ||
name: string | RegExp, | ||
qualified?: { | ||
name: RegExp; | ||
typeChecker: ts.TypeChecker; | ||
}, | ||
): boolean { | ||
const { expression } = node; | ||
if (qualified) { | ||
const type = qualified.typeChecker.getTypeAtLocation(expression); | ||
if (type) { | ||
const qualifiedName = qualified.typeChecker.getFullyQualifiedName( | ||
type.symbol, | ||
); | ||
if (!qualified.name.test(qualifiedName)) { | ||
return false; | ||
} | ||
} | ||
} | ||
const text = expression.getText(); | ||
return typeof name === 'string' ? text === name : Boolean(text.match(name)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import * as ts from 'typescript'; | ||
import { couldBeType } from '../../src/etc/could-be-type'; | ||
import { createSourceFileAndTypeChecker } from './create-source-file-and-type-checker'; | ||
|
||
describe('couldBeType', () => { | ||
it('should match a specific type', () => { | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
class A {} | ||
let a: A; | ||
`, | ||
); | ||
const node = (sourceFile.statements[1] as ts.VariableStatement).declarationList.declarations[0]; | ||
const type = typeChecker.getTypeAtLocation(node); | ||
|
||
expect(couldBeType(type, 'A')).toBe(true); | ||
}); | ||
|
||
it('should not match different types', () => { | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
class A {} | ||
class B {} | ||
let b: B; | ||
`, | ||
); | ||
const node = (sourceFile.statements[2] as ts.VariableStatement).declarationList.declarations[0]; | ||
const type = typeChecker.getTypeAtLocation(node); | ||
|
||
expect(couldBeType(type, 'A')).toBe(false); | ||
expect(couldBeType(type, 'B')).toBe(true); | ||
}); | ||
|
||
it('should match a base type', () => { | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
class A {} | ||
class B extends A {} | ||
let b: B; | ||
`, | ||
); | ||
const node = (sourceFile.statements[2] as ts.VariableStatement).declarationList.declarations[0]; | ||
const type = typeChecker.getTypeAtLocation(node); | ||
|
||
expect(couldBeType(type, 'A')).toBe(true); | ||
expect(couldBeType(type, 'B')).toBe(true); | ||
}); | ||
|
||
it('should match an implemented interface', () => { | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
interface A { name: string; } | ||
class B implements A { name = ""; } | ||
let b: B; | ||
`, | ||
); | ||
const node = (sourceFile.statements[2] as ts.VariableStatement).declarationList.declarations[0]; | ||
const type = typeChecker.getTypeAtLocation(node); | ||
|
||
expect(couldBeType(type, 'A')).toBe(true); | ||
expect(couldBeType(type, 'B')).toBe(true); | ||
}); | ||
|
||
it('should match an implemented generic interface', () => { | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
interface A<T> { value: T; } | ||
class B<T> implements A<T> { constructor(public value: T) {} } | ||
let b = new B<string>("B"); | ||
`, | ||
); | ||
const node = (sourceFile.statements[2] as ts.VariableStatement).declarationList.declarations[0]; | ||
const type = typeChecker.getTypeAtLocation(node); | ||
|
||
expect(couldBeType(type, 'A')).toBe(true); | ||
expect(couldBeType(type, 'B')).toBe(true); | ||
}); | ||
|
||
it('should match an intersection type', () => { | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
class A {} | ||
class B {} | ||
let ab: A & B; | ||
`, | ||
); | ||
const node = (sourceFile.statements[2] as ts.VariableStatement).declarationList.declarations[0]; | ||
const type = typeChecker.getTypeAtLocation(node); | ||
|
||
expect(couldBeType(type, 'A')).toBe(true); | ||
expect(couldBeType(type, 'B')).toBe(true); | ||
}); | ||
|
||
it('should match a union type', () => { | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
class A {} | ||
class B {} | ||
let ab: A | B; | ||
`, | ||
); | ||
const node = (sourceFile.statements[2] as ts.VariableStatement).declarationList.declarations[0]; | ||
const type = typeChecker.getTypeAtLocation(node); | ||
|
||
expect(couldBeType(type, 'A')).toBe(true); | ||
expect(couldBeType(type, 'B')).toBe(true); | ||
}); | ||
|
||
it.todo('should support fully-qualified types', () => { | ||
// TODO: This test is disabled because we're failing to import from other files using @typescript/vfs. See env.languageService.getSemanticDiagnostics(fileName) for error message. | ||
const { sourceFile, typeChecker } = createSourceFileAndTypeChecker( | ||
` | ||
import { A } from "./a"; | ||
class B {} | ||
let a: A; | ||
let b: B; | ||
`, | ||
); | ||
const nodeA = (sourceFile.statements[2] as ts.VariableStatement).declarationList.declarations[0]; | ||
const nodeB = (sourceFile.statements[3] as ts.VariableStatement).declarationList.declarations[0]; | ||
const typeA = typeChecker.getTypeAtLocation(nodeA); | ||
const typeB = typeChecker.getTypeAtLocation(nodeB); | ||
|
||
expect( | ||
couldBeType(typeA, 'A', { | ||
name: /"a"/, | ||
typeChecker, | ||
}), | ||
).toBe(true); | ||
expect( | ||
couldBeType(typeB, 'B', { | ||
name: /"b"/, | ||
typeChecker, | ||
}), | ||
).toBe(false); | ||
}); | ||
}); |
Oops, something went wrong.