eslint-plugin-sonarjs
Version:
83 lines (82 loc) • 3.63 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.classifyTypesByOrigin = classifyTypesByOrigin;
/*
* SonarQube JavaScript Plugin
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
const typescript_1 = __importDefault(require("typescript"));
/**
* Partitions the syntactic members of a type annotation into "internal"
* (user-writable in this project) and "external" (declared in node_modules
* or in the TypeScript default lib).
*
* Operates on the syntactic AST node, not on a resolved `ts.Type`. Returning
* the original AST nodes lets callers produce locations, suggestions, or
* fixes targeting exactly what the user wrote.
*
* Classification rules per top-level member:
* - Keyword / literal types -> internal (the user wrote them directly).
* - TSTypeReference -> resolves the type name to a symbol and inspects its
* declarations. A reference is external only when ALL declarations live in
* files that satisfy `isSourceFileFromExternalLibrary` or
* `isSourceFileDefaultLibrary`. Any local declaration (declaration-merging
* escape hatch) makes the reference internal.
* - Any other composite constructor (TSIntersectionType, TSArrayType,
* TSTypeLiteral, TSConditionalType, ...) -> internal at the top level. We
* do not recurse; callers can if they need to.
*
* Known limitation: alias chains are not followed. If the user re-aliases an
* external type locally (e.g. `type Inner = ReactNode`), the local alias is
* internal because the user has a place to edit.
*/
function classifyTypesByOrigin(typeNode, services) {
const members = typeNode.type === 'TSUnionType' ? typeNode.types : [typeNode];
const result = { internal: [], external: [] };
for (const member of members) {
if (isExternalMember(member, services)) {
result.external.push(member);
}
else {
result.internal.push(member);
}
}
return result;
}
function isExternalMember(member, services) {
if (member.type !== 'TSTypeReference') {
return false;
}
const checker = services.program.getTypeChecker();
const tsNode = services.esTreeNodeToTSNodeMap.get(member);
let symbol = checker.getSymbolAtLocation(tsNode.typeName);
// Imported names resolve to a local alias symbol pointing at the import
// statement; without following the alias, external imports would look local.
if (symbol && symbol.flags & typescript_1.default.SymbolFlags.Alias) {
symbol = checker.getAliasedSymbol(symbol);
}
if (!symbol?.declarations?.length) {
return false;
}
const program = services.program;
return symbol.declarations.every(decl => {
const sourceFile = decl.getSourceFile();
return (program.isSourceFileFromExternalLibrary(sourceFile) ||
program.isSourceFileDefaultLibrary(sourceFile));
});
}