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

Conditional compile code flow - var tracking #132

Merged
merged 2 commits into from
Sep 16, 2024
Merged
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
22 changes: 22 additions & 0 deletions src/plugins/trackCodeFlow/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,28 @@ describe('trackCodeFlow', () => {
expect(actual).deep.equal(expected);
});

it('implements assign-all-paths with conditional compilation', async () => {
const diagnostics = await linter.run({
...project1,
files: ['source/assign-all-paths-conditional-compilation.brs'],
rules: {
'assign-all-paths': 'error',
'consistent-return': 'off',
'unused-variable': 'off'
},
diagnosticFilters: [1001, 1090]
} as any);
const actual = fmtDiagnostics(diagnostics);
const expected = [
`15:LINT1003:Not all the code paths assign 'a'`,
`23:LINT1003:Not all the code paths assign 'a'`,
`42:LINT1003:Not all the code paths assign 'a'`,
`65:LINT1003:Not all the code paths assign 'a'`,
`76:LINT1003:Not all the code paths assign 'a'`
];
expect(actual).deep.equal(expected);
});

it('report errors for classes', async () => {
const diagnostics = await linter.run({
...project1,
Expand Down
66 changes: 48 additions & 18 deletions src/plugins/trackCodeFlow/varTracking.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BscFile, FunctionExpression, BsDiagnostic, Range, isForStatement, isForEachStatement, isIfStatement, isAssignmentStatement, isNamespaceStatement, NamespaceStatement, Expression, isVariableExpression, isBinaryExpression, TokenKind, Scope, CallableContainerMap, DiagnosticSeverity, isLiteralInvalid, isWhileStatement, isCatchStatement, isLabelStatement, isGotoStatement, ParseMode, util, isMethodStatement, isTryCatchStatement } from 'brighterscript';
import { BscFile, FunctionExpression, BsDiagnostic, Range, isForStatement, isForEachStatement, isIfStatement, isAssignmentStatement, isNamespaceStatement, NamespaceStatement, Expression, isVariableExpression, isBinaryExpression, TokenKind, Scope, CallableContainerMap, DiagnosticSeverity, isLiteralInvalid, isWhileStatement, isCatchStatement, isLabelStatement, isGotoStatement, ParseMode, util, isMethodStatement, isTryCatchStatement, isConditionalCompileStatement } from 'brighterscript';
import { LintState, StatementInfo, NarrowingInfo, VarInfo, VarRestriction } from '.';
import { PluginContext } from '../../util';
import { Location } from 'vscode-languageserver-types';
Expand All @@ -17,6 +17,45 @@ enum ValidationKind {
Unsafe = 'Unsafe'
}


export const VarTrackingMessages = {
varCasing: (curr: VarInfo, name: { text: string; location: Location }) => ({
severity: DiagnosticSeverity.Warning,
source: 'bslint',
code: VarLintError.CaseMismatch,
message: `Variable '${name.text}' was previously set with a different casing as '${curr.name}'`,
data: {
name: curr.name,
location: name.location
}
}),
unsafeIterator: (name: string) => ({
severity: DiagnosticSeverity.Error,
source: 'bslint',
code: VarLintError.UnsafeIteratorVar,
message: `Using iterator variable '${name}' outside loop`
}),
unusedVariable: (name: string) => ({
severity: DiagnosticSeverity.Warning,
source: 'bslint',
code: VarLintError.UnusedVariable,
message: `Variable '${name}' is set but value is never used`
}),
unsafeInitialization: (name: string) => ({
severity: DiagnosticSeverity.Error,
source: 'bslint',
code: VarLintError.UnsafeInitialization,
message: `Not all the code paths assign '${name}'`
}),
uninitializedVariable: (name: string, scopeName: string) => ({
severity: DiagnosticSeverity.Error,
source: 'bslint',
code: VarLintError.UninitializedVar,
message: `Using uninitialised variable '${name}' when this file is included in scope '${scopeName}'`
})
};


interface ValidationInfo {
kind: ValidationKind;
name: string;
Expand Down Expand Up @@ -60,14 +99,9 @@ export function createVarLinter(
function verifyVarCasing(curr: VarInfo, name: { text: string; location: Location }) {
if (curr && curr.name !== name.text) {
diagnostics.push({
...VarTrackingMessages.varCasing(curr, name),
severity: severity.caseSensitivity,
code: VarLintError.CaseMismatch,
message: `Variable '${name.text}' was previously set with a different casing as '${curr.name}'`,
location: name.location,
data: {
name: curr.name,
location: name.location
}
location: name.location
});
}
}
Expand Down Expand Up @@ -246,7 +280,7 @@ export function createVarLinter(
if (!parent.locals) {
parent.locals = locals;
} else {
const isParentBranched = isIfStatement(parent.stat) || isTryCatchStatement(parent.stat);
const isParentBranched = isIfStatement(parent.stat) || isTryCatchStatement(parent.stat) || isConditionalCompileStatement(parent.stat);
const isLoop = isForStatement(closed.stat) || isForEachStatement(closed.stat) || isWhileStatement(closed.stat);
locals.forEach((local, name) => {
const parentLocal = parent.locals.get(name);
Expand Down Expand Up @@ -302,16 +336,14 @@ export function createVarLinter(
if (local.isUnsafe && !findSafeLocal(name)) {
if (local.restriction) {
diagnostics.push({
...VarTrackingMessages.unsafeIterator(name),
severity: severity.unsafeIterators,
code: VarLintError.UnsafeIteratorVar,
message: `Using iterator variable '${name}' outside loop`,
location: expr.location
});
} else if (!isNarrowing(local, expr, parent, curr)) {
diagnostics.push({
severity: severity.unsafePathLoop,
code: VarLintError.UnsafeInitialization,
message: `Not all the code paths assign '${name}'`,
...VarTrackingMessages.unsafeInitialization(name),
severity: severity.unsafePathLoop, // should this be severity.assignAllPath?
location: expr.location
});
}
Expand Down Expand Up @@ -353,9 +385,8 @@ export function createVarLinter(
locals.forEach(local => {
if (!local.isUsed && !local.restriction) {
diagnostics.push({
...VarTrackingMessages.unusedVariable(local.name),
severity: severity.unusedVariable,
code: VarLintError.UnusedVariable,
message: `Variable '${local.name}' is set but value is never used`,
location: local.location
});
}
Expand Down Expand Up @@ -430,9 +461,8 @@ function deferredVarLinter(
case ValidationKind.UninitializedVar:
if (!hasCallable) {
diagnostics.push({
...VarTrackingMessages.uninitializedVariable(name, scope.name),
severity: DiagnosticSeverity.Error,
code: VarLintError.UninitializedVar,
message: `Using uninitialised variable '${name}' when this file is included in scope '${scope.name}'`,
location: location
});
}
Expand Down
77 changes: 77 additions & 0 deletions test/project1/source/assign-all-paths-conditional-compilation.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
function assignPathsCC() as dynamic
#if true
a = 1
#else
a = 2
#end if
return a ' no error
end function


function assignPathsCC1() as dynamic
#if true
a = 1
#end if
return a ' not really an error because #else block will never be run, but displays one anyway. Maybe fix this
end function


function assignPathsCC2() as dynamic
#if SOME_VAR
a = 1
#end if
return a ' error
end function


function assignPathsCC3() as dynamic
#if SOME_VAR
a = 1
#else
a = 2
#end if
return a ' no error
end function

function assignPathsCC4() as dynamic
#if SOME_VAR
b = 1
#else
a = 2
#end if
return a ' error
end function


function assignPathsCC5() as dynamic
#if SOME_VAR
a = 1
#else if SOME_VAR2
a = 2
#else
a = 3
#end if
return a ' No error
end function

function assignPathsCC6() as dynamic
#if SOME_VAR
a = 1
#else if SOME_VAR2
a = 2
#else
' missing assignment
#end if
return a ' error
end function

function assignPathsCC7() as dynamic
#if SOME_VAR
a = 1
#else if SOME_VAR2
' missing assignment
#else
a = 2
#end if
return a ' error
end function