Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Improve import declaration deprecation checks #207

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 89 additions & 22 deletions src/linter/ui5Types/SourceFileLinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,32 +419,73 @@ export default class SourceFileLinter {
return;
}
const symbol = this.#checker.getSymbolAtLocation(moduleSpecifierNode);
// Only check for the "default" export regardless of what's declared
// as UI5 / AMD only supports importing the default anyways.
// TODO: This needs to be enhanced in future
const defaultExportSymbol = symbol?.exports?.get("default" as ts.__String);
const deprecationInfo = this.getDeprecationInfo(defaultExportSymbol);
if (deprecationInfo) {

if (this.isSymbolOfPseudoType(symbol)) {
this.#reporter.addMessage({
node: moduleSpecifierNode,
severity: LintMessageSeverity.Error,
ruleId: "ui5-linter-no-deprecated-api",
ruleId: "ui5-linter-no-pseudo-modules",
message:
`Import of deprecated module ` +
`Import of pseudo module ` +
`'${moduleSpecifierNode.text}'`,
messageDetails: deprecationInfo.messageDetails,
messageDetails: "Import library and reuse the enum from there",
});
return;
}

if (this.isSymbolOfPseudoType(symbol)) {
// Check whether all exports are marked as deprecated, as otherwise we might create a false positive.
// TODO TS: This needs to be enhanced for checks of TypeScript sources to only check for the actual
// imports (default, *, named)
if (!symbol?.exports) {
return;
}
let hasDeprecatedExports = false;
const messageDetails: string[] = [];
for (const exportSymbol of symbol.exports.values()) {
const deprecationInfo = this.getDeprecationInfo(exportSymbol);
if (deprecationInfo) {
hasDeprecatedExports = true;
if (deprecationInfo.messageDetails) {
messageDetails.push(deprecationInfo.messageDetails);
}
continue;
} else if (
exportSymbol.flags === ts.SymbolFlags.ValueModule ||
exportSymbol.flags === ts.SymbolFlags.NamespaceModule
) {
// A namespace can be a "ValueModule" or a "NamespaceModule".
// The namespace itself is most likely not deprecated,
// but all symbols exported by the namespace might be.
if (exportSymbol.exports) {
for (const namespacedExportSymbol of exportSymbol.exports.values()) {
const namespacedDeprecationInfo = this.getDeprecationInfo(namespacedExportSymbol);
if (namespacedDeprecationInfo) {
hasDeprecatedExports = true;
if (namespacedDeprecationInfo.messageDetails) {
messageDetails.push(namespacedDeprecationInfo.messageDetails);
}
continue;
}
// For now, all exports must be deprecated for the import to be considered deprecated
// See TODO TS above for future enhancements
return;
}
continue;
}
}
// For now, all exports must be deprecated for the import to be considered deprecated
// See TODO TS above for future enhancements
return;
}
if (hasDeprecatedExports) {
this.#reporter.addMessage({
node: moduleSpecifierNode,
severity: LintMessageSeverity.Error,
ruleId: "ui5-linter-no-pseudo-modules",
ruleId: "ui5-linter-no-deprecated-api",
message:
`Import of pseudo module ` +
`'${moduleSpecifierNode.text}'`,
messageDetails: "Import library and reuse the enum from there",
`Import of deprecated module ` +
`'${moduleSpecifierNode.text}'`,
messageDetails: messageDetails.length ? messageDetails.join("\n") : undefined,
});
}
}
Expand All @@ -453,9 +494,14 @@ export default class SourceFileLinter {
if (symbol.name.startsWith("sap/")) {
return true;
} else {
const sourceFile = symbol.valueDeclaration?.getSourceFile();
if (sourceFile?.fileName.match(/@openui5|@sapui5|@ui5/)) {
return true;
const declarations = symbol.getDeclarations();
if (declarations) {
for (const declaration of declarations) {
const sourceFile = declaration.getSourceFile();
if (sourceFile.fileName.match(/@openui5|@sapui5|@ui5/)) {
return true;
}
}
}
}
return false;
Expand All @@ -465,20 +511,41 @@ export default class SourceFileLinter {
if (symbol.name.startsWith("sap/")) {
return true;
} else {
const sourceFile = symbol.valueDeclaration?.getSourceFile();
if (sourceFile?.fileName.match(/@openui5|@sapui5|@ui5|@types\/jquery/)) {
return true;
const declarations = symbol.getDeclarations();
if (declarations) {
for (const declaration of declarations) {
const sourceFile = declaration.getSourceFile();
if (sourceFile.fileName.match(/@openui5|@sapui5|@ui5|@types\/jquery/)) {
return true;
}
}
}
}
return false;
}

isSymbolOfJquerySapType(symbol: ts.Symbol) {
return symbol.valueDeclaration?.getSourceFile().fileName === "/types/@ui5/linter/overrides/jquery.sap.d.ts";
const declarations = symbol.getDeclarations();
if (declarations) {
for (const declaration of declarations) {
const sourceFile = declaration.getSourceFile();
if (sourceFile.fileName === "/types/@ui5/linter/overrides/jquery.sap.d.ts") {
return true;
}
}
}
}

isSymbolOfPseudoType(symbol: ts.Symbol | undefined) {
return symbol?.valueDeclaration?.getSourceFile().fileName.startsWith("/types/@ui5/linter/overrides/library/");
const declarations = symbol?.getDeclarations();
if (declarations) {
for (const declaration of declarations) {
const sourceFile = declaration.getSourceFile();
if (sourceFile.fileName.startsWith("/types/@ui5/linter/overrides/library/")) {
return true;
}
}
}
}

findClassOrInterface(node: ts.Node): ts.Type | undefined {
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/linter/rules/NoDeprecatedApi/NoDeprecatedApi.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sap.ui.define([
"sap/m/Button", "sap/m/DateTimeInput", "sap/base/util/includes", "sap/ui/Device", "sap/ui/core/library", "sap/ui/generic/app/navigation/service/NavigationHandler",
"sap/ui/table/Table", "sap/ui/table/plugins/MultiSelectionPlugin", "sap/ui/core/Configuration", "sap/m/library"
], function(Button, DateTimeInput, includes, Device, coreLib, NavigationHandler, Table, MultiSelectionPlugin, Configuration, mobileLib) {
"sap/ui/table/Table", "sap/ui/table/plugins/MultiSelectionPlugin", "sap/ui/core/Configuration", "sap/m/library", "sap/ui/commons/library"
], function(Button, DateTimeInput, includes, Device, coreLib, NavigationHandler, Table, MultiSelectionPlugin, Configuration, mobileLib, commonsLib) {

var dateTimeInput = new DateTimeInput(); // TODO detect: Control is deprecated

Expand Down