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

Add warning diagnostic for V2 API use #35

Merged
merged 10 commits into from
May 23, 2024
112 changes: 112 additions & 0 deletions packages/pyright-internal/src/analyzer/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,7 @@ export class Checker extends ParseTreeWalker {
override visitName(node: NameNode) {
// Determine if we should log information about private usage.
this._conditionallyReportPrivateUsage(node);
this._reportMicrobitVersionApiUnsupported(node);

// Determine if the name is possibly unbound.
if (!this._isUnboundCheckSuppressed) {
Expand Down Expand Up @@ -1238,6 +1239,7 @@ export class Checker extends ParseTreeWalker {
override visitMemberAccess(node: MemberAccessNode) {
this._evaluator.getType(node);
this._conditionallyReportPrivateUsage(node.memberName);
this._reportMicrobitVersionApiUnsupported(node.memberName);

// Walk the leftExpression but not the memberName.
this.walk(node.leftExpression);
Expand All @@ -1247,13 +1249,16 @@ export class Checker extends ParseTreeWalker {

override visitImportAs(node: ImportAsNode): boolean {
this._evaluator.evaluateTypesForStatement(node);
this._reportMicrobitVersionApiUnsupported(node.module.nameParts[0]);

return false;
}

override visitImportFrom(node: ImportFromNode): boolean {
if (!node.isWildcardImport) {
node.imports.forEach((importAs) => {
this._evaluator.evaluateTypesForStatement(importAs);
this._reportMicrobitVersionApiUnsupported(importAs.alias ?? importAs.name);
});
} else {
const importInfo = AnalyzerNodeInfo.getImportInfo(node.module);
Expand All @@ -1272,6 +1277,7 @@ export class Checker extends ParseTreeWalker {
);
}
}
this._reportMicrobitVersionApiUnsupported(node.module.nameParts[0]);

return false;
}
Expand Down Expand Up @@ -4937,4 +4943,110 @@ export class Checker extends ParseTreeWalker {
}
});
}

private _reportMicrobitVersionApiUnsupported(node?: NameNode) {
if (!node || this._fileInfo.isStubFile) {
return;
}
const type = this._evaluator.getType(node);
if (!type || type.category === TypeCategory.Unknown) {
return;
}
const declarations = this._evaluator.getDeclarationsForNameNode(node);
let primaryDeclaration =
declarations && declarations.length > 0 ? declarations[declarations.length - 1] : undefined;
if (!primaryDeclaration || primaryDeclaration.node === node) {
return;
}
if (primaryDeclaration.type === DeclarationType.Alias) {
primaryDeclaration = this._evaluator.resolveAliasDeclaration(
primaryDeclaration,
/* resolveLocalNames */ true
);
}
if (primaryDeclaration && primaryDeclaration.node !== node) {
switch (primaryDeclaration.type) {
case DeclarationType.Class /* fallthrough */:
microbit-matt-hillsdon marked this conversation as resolved.
Show resolved Hide resolved
microbit-matt-hillsdon marked this conversation as resolved.
Show resolved Hide resolved
return this._reportMicrobitVersionApiUnsupportedCheck(
node,
primaryDeclaration.moduleName,
primaryDeclaration.node.name.value
);
case DeclarationType.Function: {
const name = primaryDeclaration.node.name.value;
const className = primaryDeclaration.isMethod
? ParseTreeUtils.getEnclosingClass(primaryDeclaration.node)?.name.value
: undefined;
const dottedName = className ? `${className}.${name}` : name;
return this._reportMicrobitVersionApiUnsupportedCheck(
node,
primaryDeclaration.moduleName,
dottedName,
primaryDeclaration.isMethod ? dottedName : undefined
);
}
case DeclarationType.Variable:
if (primaryDeclaration.node.nodeType === ParseNodeType.Name) {
return this._reportMicrobitVersionApiUnsupportedCheck(
node,
primaryDeclaration.moduleName,
primaryDeclaration.node.value
);
}
break;
}
}

if (isModule(type)) {
return this._reportMicrobitVersionApiUnsupportedCheck(node, type.moduleName);
}
}

private _reportMicrobitVersionApiUnsupportedCheck(
node: NameNode,
moduleName: string,
name?: string,
nameForError?: string
) {
const fullName = moduleName + (name ? '.' + name : '');
if (this._microbitV2OnlyNames.has(moduleName)) {
this._reportMicrobitVersionApiUnsupportedDiagnostic(node, nameForError ?? fullName);
} else if (this._microbitV2OnlyNames.has(fullName)) {
this._reportMicrobitVersionApiUnsupportedDiagnostic(node, nameForError ?? fullName);
}
}

private _reportMicrobitVersionApiUnsupportedDiagnostic(node: NameNode, name: string): void {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportMicrobitVersionApiUnsupported,
DiagnosticRule.reportMicrobitVersionApiUnsupported,
Localizer.Diagnostic.microbitVersionApiUnsupported().format({
name: name.replace(/^microbit\./, ''),
device: 'micro:bit V1',
}),
node
);
}

// Potentially we could move these to decorators if it didn't impact users
private _microbitV2OnlyNames = new Set([
'microbit.microphone',
'microbit.speaker',
'microbit.run_every',
'microbit.set_volume',
'microbit.Sound',
'microbit.SoundEvent',
'microbit.pin_logo',
'microbit.pin_speaker',
'microbit.audio.SoundEffect',

'log',

'power',

'audio.SoundEffect',

'neopixel.NeoPixel.fill',
'neopixel.NeoPixel.write',
]);
}
7 changes: 7 additions & 0 deletions packages/pyright-internal/src/common/configOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ export interface DiagnosticRuleSet {
// Report cases where the a "match" statement is not exhaustive in
// covering all possible cases.
reportMatchNotExhaustive: DiagnosticLevel;

// Report micro:bit V2 API use.
reportMicrobitVersionApiUnsupported: DiagnosticLevel;
}

export function cloneDiagnosticRuleSet(diagSettings: DiagnosticRuleSet): DiagnosticRuleSet {
Expand Down Expand Up @@ -374,6 +377,7 @@ export function getDiagLevelDiagnosticRules() {
DiagnosticRule.reportUnusedCoroutine,
DiagnosticRule.reportUnnecessaryTypeIgnoreComment,
DiagnosticRule.reportMatchNotExhaustive,
DiagnosticRule.reportMicrobitVersionApiUnsupported,
];
}

Expand Down Expand Up @@ -453,6 +457,7 @@ export function getOffDiagnosticRuleSet(): DiagnosticRuleSet {
reportUnusedCoroutine: 'none',
reportUnnecessaryTypeIgnoreComment: 'none',
reportMatchNotExhaustive: 'none',
reportMicrobitVersionApiUnsupported: 'none',
};

return diagSettings;
Expand Down Expand Up @@ -528,6 +533,7 @@ export function getBasicDiagnosticRuleSet(): DiagnosticRuleSet {
reportUnusedCoroutine: 'error',
reportUnnecessaryTypeIgnoreComment: 'none',
reportMatchNotExhaustive: 'none',
reportMicrobitVersionApiUnsupported: 'warning',
};

return diagSettings;
Expand Down Expand Up @@ -603,6 +609,7 @@ export function getStrictDiagnosticRuleSet(): DiagnosticRuleSet {
reportUnusedCoroutine: 'error',
reportUnnecessaryTypeIgnoreComment: 'none',
reportMatchNotExhaustive: 'error',
reportMicrobitVersionApiUnsupported: 'warning',
};

return diagSettings;
Expand Down
2 changes: 2 additions & 0 deletions packages/pyright-internal/src/common/diagnosticRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,6 @@ export enum DiagnosticRule {
reportUnusedCoroutine = 'reportUnusedCoroutine',
reportUnnecessaryTypeIgnoreComment = 'reportUnnecessaryTypeIgnoreComment',
reportMatchNotExhaustive = 'reportMatchNotExhaustive',

reportMicrobitVersionApiUnsupported = 'reportMicrobitVersionApiUnsupported',
}
4 changes: 4 additions & 0 deletions packages/pyright-internal/src/localization/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,10 @@ export namespace Localizer {
);
export const methodReturnsNonObject = () =>
new ParameterizedString<{ name: string }>(getRawString('Diagnostic.methodReturnsNonObject'));
export const microbitVersionApiUnsupported = () =>
new ParameterizedString<{ name: string; device: string }>(
getRawString('Diagnostic.microbitVersionApiUnsupported')
);
export const missingProtocolMembers = () => getRawString('Diagnostic.missingProtocolMembers');
export const missingSuperCall = () =>
new ParameterizedString<{ methodName: string }>(getRawString('Diagnostic.missingSuperCall'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@
"moduleAsType": "Module cannot be used as a type",
"moduleNotCallable": "Module is not callable",
"moduleUnknownMember": "\"{name}\" is not a known member of module \"{module}\"",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"namedExceptAfterCatchAll": "A named except clause cannot appear after catch-all except clause",
"namedParamAfterParamSpecArgs": "Keyword parameter \"{name}\" cannot appear in signature after ParamSpec args parameter",
"namedTupleEmptyName": "Names within a named tuple cannot be empty",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "No es pot assignar el membre \"{name}\" per al tipus \"{type}\"",
"moduleNotCallable": "El mòdul no es pot cridar",
"moduleUnknownMember": "\"{name}\" no és un membre conegut del mòdul \"{module}\"",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "L'argument no predeterminat va després de l'argument predeterminat",
"noOverload": "Els arguments no coincideixen amb els tipus de paràmetres",
"objectNotCallable": "L'objecte no es pot cridar",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "Kann Mitglied \"{name}\" nicht für Typ \"{type}\" zuweisen",
"moduleNotCallable": "Modul ist nicht abrufbar",
"moduleUnknownMember": "„{name}“ ist kein bekanntes Mitglied des Moduls „{module}“",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "Nicht-Standard Argument folgt Standardargument",
"noOverload": "Argumente stimmen nicht mit Parametertypen überein",
"objectNotCallable": "Objekt ist nicht abrufbar",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "Cannot assign member \"{name}\" for type \"{type}\"",
"moduleNotCallable": "Module is not callable",
"moduleUnknownMember": "\"{name}\" is not a known member of module \"{module}\"",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "Non-default argument follows default argument",
"noOverload": "Arguments do not match parameter types",
"objectNotCallable": "Object is not callable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "No se puede asignar el miembro \"{name}\" para el tipo \"{type}\"",
"moduleNotCallable": "No se puede llamar al módulo",
"moduleUnknownMember": "\"{name}\" no es un miembro conocido del módulo \"{module}\"",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "Argumento no por defecto después de argumento por defecto",
"noOverload": "Los tipos de los argumentos no coinciden con los tipos de los parámetros",
"objectNotCallable": "No se puede llamar al objeto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "Impossible d'affecter le membre \"{name}\" pour le type \"{type}\"",
"moduleNotCallable": "Le module n'est pas appelable",
"moduleUnknownMember": "\"{name}\" n'est pas un membre connu du module \"{module}\"",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "Argument sans valeur par défaut suit un argument ayant une valeur par défaut",
"noOverload": "Les arguments ne correspondent pas aux types des paramètres",
"objectNotCallable": "L'objet n'est pas appelable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "型「{type}」の メンバー「{name}」 を割り当てできません",
"moduleNotCallable": "モジュールは呼び出しできません",
"moduleUnknownMember": "「{name}」はモジュール「{module}」の既知のメンバではありません",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "デフォルトでない引数はデフォルト引数の後に続きます",
"noOverload": "引数がパラメータ型と一致しません",
"objectNotCallable": "オブジェクトは呼び出しできません",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "\"{type}\" 유형으로 \"{name}\" 멤버를 할당할 수 없음",
"moduleNotCallable": "모듈을 호출할 수 없음",
"moduleUnknownMember": "\"{name}\"(이)가 \"{module}\"의 기존 멤버가 아님",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "기본값 인자 뒤에 기본값이 아닌 인자가 입력됨",
"noOverload": "인자가 매개변수 유형과 일치하지 않음",
"objectNotCallable": "개체를 호출할 수 없음",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "crwdns331292:0{name}crwdnd331292:0{type}crwdne331292:0",
"moduleNotCallable": "crwdns331294:0crwdne331294:0",
"moduleUnknownMember": "crwdns331296:0{name}crwdnd331296:0{module}crwdne331296:0",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "crwdns331298:0crwdne331298:0",
"noOverload": "crwdns331300:0crwdne331300:0",
"objectNotCallable": "crwdns331302:0crwdne331302:0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "Kan lid \"{name}\" niet toewijzen voor type \"{type}\"",
"moduleNotCallable": "Module kan niet aangeroepen worden",
"moduleUnknownMember": "\"{name}\" is geen bekend lid van module \"{module}\"",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "Niet standaard argument volgt het standaard argument",
"noOverload": "Argumenten komen niet overeen met parametertypes",
"objectNotCallable": "Object kan niet aangeroepen worden",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "无法为类型 “{type}” 分配成员 “{name}”",
"moduleNotCallable": "模块不可调用",
"moduleUnknownMember": "\"{name}\" 不是模块 \"{module}\" 的已知成员",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "默认参数之后为非默认参数",
"noOverload": "参数与参数类型不匹配",
"objectNotCallable": "对象不可调用",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"memberSet": "無法指派類型 \"{type}\" 的成員 \"{name}\"",
"moduleNotCallable": "模組不可呼叫",
"moduleUnknownMember": "\"{name}\" 不是模組 \"{module}\" 的已知成員",
"microbitVersionApiUnsupported": "\"{name}\" is not supported on a {device}",
"nonDefaultAfterDefault": "非預設引數接在預設引數後面",
"noOverload": "引數與參數類型不相符",
"objectNotCallable": "物件不可呼叫",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/// <reference path="fourslash.ts" />

// @filename: microbit/__init__.py
// @library true
//// from . import audio as audio
//// def run_every():
//// pass
////
//// class Sound:
//// GIGGLE = 1

// @filename: microbit/audio.py
// @library true
//// class SoundEffect:
//// pass

// @filename: log.py
// @library true
//// def add():
//// pass

// @filename: neopixel.py
// @library true
//// class NeoPixel:
//// def fill(self):
//// pass

// @filename: test0.py
//// from microbit import *
//// [|/*wildcardUse*/run_every|]()

// @filename: test1.py
//// from microbit import [|/*importFrom*/run_every|]
//// [|/*functionCall*/run_every|]()

// @filename: test2.py
//// import [|/*import*/log|]
//// [|/*moduleReference*/log|].[|/*functionCall2*/add|]()

// @filename: test3.py
//// import neopixel
//// np = neopixel.NeoPixel()
//// np.[|/*methodCall*/fill|]()

// @filename: test4.py
//// from microbit import *
//// [|/*classRef*/Sound|].GIGGLE
//// audio.[|/*constructor*/SoundEffect|]()

// @ts-ignore
await helper.verifyDiagnostics({
wildcardUse: { category: 'warning', message: `"run_every" is not supported on a micro:bit V1` },
importFrom: { category: 'warning', message: `"run_every" is not supported on a micro:bit V1` },
functionCall: { category: 'warning', message: `"run_every" is not supported on a micro:bit V1` },
import: { category: 'warning', message: `"log" is not supported on a micro:bit V1` },
moduleReference: { category: 'warning', message: `"log" is not supported on a micro:bit V1` },
functionCall2: { category: 'warning', message: `"log.add" is not supported on a micro:bit V1` },
methodCall: { category: 'warning', message: `"NeoPixel.fill" is not supported on a micro:bit V1` },
classRef: { category: 'warning', message: `"Sound" is not supported on a micro:bit V1` },
constructor: { category: 'warning', message: `"audio.SoundEffect" is not supported on a micro:bit V1` },
});
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,11 @@ export class TestState {

// organize things per file
const resultPerFile = this._getDiagnosticsPerFile();
Array.from(resultPerFile.keys()).forEach((k) => {
resultPerFile.get(k)?.warnings.forEach((e) => {
console.log(k, e);
});
});
microbit-matt-hillsdon marked this conversation as resolved.
Show resolved Hide resolved
const rangePerFile = this.createMultiMap<Range>(this.getRanges(), (r) => r.fileName);

if (!hasDiagnostics(resultPerFile) && rangePerFile.size === 0) {
Expand Down
11 changes: 11 additions & 0 deletions packages/vscode-pyright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,17 @@
"warning",
"error"
]
},
"reportMicrobitVersionApiUnsupported": {
"type": "string",
"description": "Controls reporting of micro:bit API use that isn't available on all devices",
"default": "warning",
"enum": [
"none",
"information",
"warning",
"error"
]
}
}
},
Expand Down
Loading