Enforce ILocalizedString usage with f1 property via types (#162991)

* Enforce ILocalizedString usage with f1 property via types

* complete comment

* Visit all symbols when encountering a union type

* use ILocalizedString

* fix tests

Co-authored-by: Alex Dima <alexdima@microsoft.com>
This commit is contained in:
Tyler James Leonhardt
2022-10-11 21:24:43 -07:00
committed by GitHub
parent 24b8eb0d54
commit 3e4e351816
8 changed files with 183 additions and 122 deletions

View File

@@ -490,54 +490,56 @@ function markNodes(ts, languageService, options) {
}
const nodeSourceFile = node.getSourceFile();
const loop = (node) => {
const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node);
if (symbolImportNode) {
setColor(symbolImportNode, 2 /* NodeColor.Black */);
const importDeclarationNode = findParentImportDeclaration(symbolImportNode);
if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) {
enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text);
const symbols = getRealNodeSymbol(ts, checker, node);
for (const { symbol, symbolImportNode } of symbols) {
if (symbolImportNode) {
setColor(symbolImportNode, 2 /* NodeColor.Black */);
const importDeclarationNode = findParentImportDeclaration(symbolImportNode);
if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) {
enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text);
}
}
}
if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) {
for (let i = 0, len = symbol.declarations.length; i < len; i++) {
const declaration = symbol.declarations[i];
if (ts.isSourceFile(declaration)) {
// Do not enqueue full source files
// (they can be the declaration of a module import)
continue;
}
if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) {
enqueue_black(declaration.name);
for (let j = 0; j < declaration.members.length; j++) {
const member = declaration.members[j];
const memberName = member.name ? member.name.getText() : null;
if (ts.isConstructorDeclaration(member)
|| ts.isConstructSignatureDeclaration(member)
|| ts.isIndexSignatureDeclaration(member)
|| ts.isCallSignatureDeclaration(member)
|| memberName === '[Symbol.iterator]'
|| memberName === '[Symbol.toStringTag]'
|| memberName === 'toJSON'
|| memberName === 'toString'
|| memberName === 'dispose' // TODO: keeping all `dispose` methods
|| /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`...
) {
enqueue_black(member);
if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) {
for (let i = 0, len = symbol.declarations.length; i < len; i++) {
const declaration = symbol.declarations[i];
if (ts.isSourceFile(declaration)) {
// Do not enqueue full source files
// (they can be the declaration of a module import)
continue;
}
if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) {
enqueue_black(declaration.name);
for (let j = 0; j < declaration.members.length; j++) {
const member = declaration.members[j];
const memberName = member.name ? member.name.getText() : null;
if (ts.isConstructorDeclaration(member)
|| ts.isConstructSignatureDeclaration(member)
|| ts.isIndexSignatureDeclaration(member)
|| ts.isCallSignatureDeclaration(member)
|| memberName === '[Symbol.iterator]'
|| memberName === '[Symbol.toStringTag]'
|| memberName === 'toJSON'
|| memberName === 'toString'
|| memberName === 'dispose' // TODO: keeping all `dispose` methods
|| /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`...
) {
enqueue_black(member);
}
if (isStaticMemberWithSideEffects(ts, member)) {
enqueue_black(member);
}
}
if (isStaticMemberWithSideEffects(ts, member)) {
enqueue_black(member);
// queue the heritage clauses
if (declaration.heritageClauses) {
for (const heritageClause of declaration.heritageClauses) {
enqueue_black(heritageClause);
}
}
}
// queue the heritage clauses
if (declaration.heritageClauses) {
for (const heritageClause of declaration.heritageClauses) {
enqueue_black(heritageClause);
}
else {
enqueue_black(declaration);
}
}
else {
enqueue_black(declaration);
}
}
}
node.forEachChild(loop);
@@ -736,13 +738,20 @@ function findSymbolFromHeritageType(ts, checker, type) {
return findSymbolFromHeritageType(ts, checker, type.expression);
}
if (ts.isIdentifier(type)) {
return getRealNodeSymbol(ts, checker, type)[0];
const tmp = getRealNodeSymbol(ts, checker, type);
return (tmp.length > 0 ? tmp[0].symbol : null);
}
if (ts.isPropertyAccessExpression(type)) {
return findSymbolFromHeritageType(ts, checker, type.name);
}
return null;
}
class SymbolImportTuple {
constructor(symbol, symbolImportNode) {
this.symbol = symbol;
this.symbolImportNode = symbolImportNode;
}
}
/**
* Returns the node's symbol and the `import` node (if the symbol resolved from a different module)
*/
@@ -774,7 +783,7 @@ function getRealNodeSymbol(ts, checker, node) {
}
if (!ts.isShorthandPropertyAssignment(node)) {
if (node.getChildCount() !== 0) {
return [null, null];
return [];
}
}
const { parent } = node;
@@ -820,10 +829,7 @@ function getRealNodeSymbol(ts, checker, node) {
const type = checker.getTypeAtLocation(parent.parent);
if (name && type) {
if (type.isUnion()) {
const prop = type.types[0].getProperty(name);
if (prop) {
symbol = prop;
}
return generateMultipleSymbols(type, name, importNode);
}
else {
const prop = type.getProperty(name);
@@ -854,9 +860,19 @@ function getRealNodeSymbol(ts, checker, node) {
}
}
if (symbol && symbol.declarations) {
return [symbol, importNode];
return [new SymbolImportTuple(symbol, importNode)];
}
return [];
function generateMultipleSymbols(type, name, importNode) {
const result = [];
for (const t of type.types) {
const prop = t.getProperty(name);
if (prop && prop.declarations) {
result.push(new SymbolImportTuple(prop, importNode));
}
}
return result;
}
return [null, null];
}
/** Get the token whose text contains the position */
function getTokenAtPosition(ts, sourceFile, position, allowPositionInLeadingTrivia, includeEndPosition) {