···
export const UNUSED_FIELD_CODE = 52005;
+
const unwrapAbstractType = (type: ts.Type) => {
+
return type.isUnionOrIntersection()
+
? type.types.find(type => type.flags & ts.TypeFlags.Object) || type
+
const getVariableDeclaration = (
+
): ts.VariableDeclaration | undefined => {
+
let node: ts.Node = start;
+
const seen = new Set();
+
while (node.parent && !seen.has(node)) {
+
if (ts.isBlock(node)) {
+
return; // NOTE: We never want to traverse up into a new function/module block
+
} else if (ts.isVariableDeclaration((node = node.parent))) {
const traverseArrayDestructuring = (
···
originalWip: Array<string>,
allFields: Array<string>,
info: ts.server.PluginCreateInfo
+
if (ts.isObjectBindingPattern(node)) {
+
return traverseDestructuring(node, originalWip, allFields, source, info);
+
} else if (ts.isArrayBindingPattern(node)) {
+
return traverseArrayDestructuring(
let results: string[] = [];
const references = info.languageService.getReferencesAtPosition(
···
ts.isBinaryExpression(foundRef)
if (ts.isVariableDeclaration(foundRef)) {
+
return crawlScope(foundRef.name, pathParts, allFields, source, info);
ts.isIdentifier(foundRef) &&
!pathParts.includes(foundRef.text)
···
foundRef.name.text === 'every' || foundRef.name.text === 'some';
const callExpression = foundRef.parent;
+
let func: ts.Expression | ts.FunctionDeclaration =
+
callExpression.arguments[0];
+
if (ts.isIdentifier(func)) {
+
// TODO: Scope utilities in checkFieldUsageInFile to deduplicate
+
const checker = info.languageService.getProgram()!.getTypeChecker();
+
checker.getSymbolAtLocation(func)?.valueDeclaration;
+
if (declaration && ts.isFunctionDeclaration(declaration)) {
+
ts.isVariableDeclaration(declaration) &&
+
declaration.initializer
+
func = declaration.initializer;
+
ts.isFunctionDeclaration(func) ||
+
ts.isFunctionExpression(func) ||
+
ts.isArrowFunction(func)
const param = func.parameters[isReduce ? 1 : 0];
···
ts.isPropertyAccessExpression(foundRef) &&
···
const defaultReservedKeys = ['id', '_id', '__typename'];
const additionalKeys = info.config.reservedKeys ?? [];
const reservedKeys = new Set([...defaultReservedKeys, ...additionalKeys]);
+
const checker = info.languageService.getProgram()?.getTypeChecker();
···
const variableDeclaration = getVariableDeclaration(node);
+
if (!variableDeclaration) return;
+
let dataType: ts.Type | undefined;
+
const type = checker.getTypeAtLocation(node.parent) as
+
// Attempt to retrieve type from internally resolve type arguments
+
if ('target' in type) {
+
const typeArguments = (type as any)
+
.resolvedTypeArguments as readonly ts.Type[];
+
dataType = typeArguments.length > 1 ? typeArguments[0] : undefined;
+
// Fallback to resolving the type from scratch
+
const apiTypeSymbol = type.getProperty('__apiType');
+
let apiType = checker.getTypeOfSymbol(apiTypeSymbol);
+
let callSignature: ts.Signature | undefined =
+
type.getCallSignatures()[0];
+
if (apiType.isUnionOrIntersection()) {
+
for (const type of apiType.types) {
+
callSignature = type.getCallSignatures()[0];
+
dataType = callSignature.getReturnType();
+
dataType = callSignature && callSignature.getReturnType();
const references = info.languageService.getReferencesAtPosition(
variableDeclaration.name.getStart()
const allAccess: string[] = [];
···
references.forEach(ref => {
if (ref.fileName !== source.fileName) return;
+
const targetNode = findNode(source, ref.textSpan.start);
+
if (!targetNode) return;
+
// Skip declaration as reference of itself
+
if (targetNode.parent === variableDeclaration) return;
+
const scopeSymbols = checker.getSymbolsInScope(
+
ts.SymbolFlags.BlockScopedVariable
+
let scopeDataSymbol: ts.Symbol | undefined;
+
for (let scopeSymbol of scopeSymbols) {
+
if (!scopeSymbol.valueDeclaration) continue;
+
let typeOfScopeSymbol = unwrapAbstractType(
+
checker.getTypeOfSymbol(scopeSymbol)
+
if (dataType === typeOfScopeSymbol) {
+
scopeDataSymbol = scopeSymbol;
+
// NOTE: This is an aggressive fallback for hooks where the return value isn't destructured
+
// This is a last resort solution for patterns like react-query, where the fallback that
+
// would otherwise happen below isn't sufficient
+
if (typeOfScopeSymbol.flags & ts.TypeFlags.Object) {
+
const tuplePropertySymbol = typeOfScopeSymbol.getProperty('0');
+
if (tuplePropertySymbol) {
+
typeOfScopeSymbol = checker.getTypeOfSymbol(tuplePropertySymbol);
+
if (dataType === typeOfScopeSymbol) {
+
scopeDataSymbol = scopeSymbol;
+
const dataPropertySymbol = typeOfScopeSymbol.getProperty('data');
+
if (dataPropertySymbol) {
+
typeOfScopeSymbol = unwrapAbstractType(
+
checker.getTypeOfSymbol(dataPropertySymbol)
+
if (dataType === typeOfScopeSymbol) {
+
scopeDataSymbol = scopeSymbol;
+
const valueDeclaration = scopeDataSymbol?.valueDeclaration;
+
let name: ts.BindingName | undefined;
+
'name' in valueDeclaration &&
+
!!valueDeclaration.name &&
+
(ts.isIdentifier(valueDeclaration.name as any) ||
+
ts.isBindingName(valueDeclaration.name as any))
+
name = valueDeclaration.name as ts.BindingName;
+
// Fall back to looking at the variable declaration directly,
+
const variableDeclaration = getVariableDeclaration(targetNode);
+
if (variableDeclaration) name = variableDeclaration.name;
+
const result = crawlScope(name, [], allPaths, source, info);
allAccess.push(...result);
+
if (!allAccess.length) {
const unused = allPaths.filter(x => !allAccess.includes(x));