Skip to content

Commit

Permalink
Merge branch 'main' into kill-the-bucket
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] committed Sep 28, 2023
2 parents 0ad992c + 0c66a20 commit a4d1cee
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 38 deletions.
11 changes: 7 additions & 4 deletions packages/awslint/bin/awslint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as chalk from 'chalk';
import * as fs from 'fs-extra';
import * as reflect from 'jsii-reflect';
import * as yargs from 'yargs';
import { ALL_RULES_LINTER, DiagnosticLevel } from '../lib';
import { ALL_RULES_LINTER, DiagnosticLevel, RuleFilterSet } from '../lib';

let stackTrace = false;

Expand Down Expand Up @@ -37,7 +37,7 @@ async function main() {
.example('awslint', 'lints the current module against all rules')
.example('awslint -v -i "resource*" -i "import*"', 'lints against all rules that start with "resource" or "import" and print successes')
.example('awslint -x "*:@aws-cdk/aws-s3*"', 'evaluate all rules in all scopes besides ones that begin with "@aws-cdk/aws-s3"')
.example('awslint --save', 'updated "package.json" with "exclude"s for all failing rules');
.example('awslint --save', 'updated "awslint.json" with "exclude"s for all failing rules');

if (!process.stdout.isTTY) {
// Disable chalk color highlighting
Expand Down Expand Up @@ -118,11 +118,14 @@ async function main() {
const excludesToSave = new Array<string>();
let errors = 0;

const includeRules = new RuleFilterSet(args.include);
const excludeRules = new RuleFilterSet(args.exclude);

const results = [];

results.push(...ALL_RULES_LINTER.eval(assembly, {
include: args.include,
exclude: args.exclude,
includeRules: includeRules,
excludeRules: excludeRules,
}));

// Sort errors to the top (highest severity first)
Expand Down
1 change: 1 addition & 0 deletions packages/awslint/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './linter';
export * from './rules';
export * from './rule-specs';
42 changes: 8 additions & 34 deletions packages/awslint/lib/linter.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import * as util from 'util';
import { PrimitiveType } from '@jsii/spec';
import * as reflect from 'jsii-reflect';
import { RuleFilterSet } from './rule-specs';

export interface LinterOptions {
/**
* List of rules to include.
* @default all rules
*/
include?: string[];
includeRules?: RuleFilterSet;

/**
* List of rules to exclude (takes precedence on "include")
* @default none
*/
exclude?: string[];
excludeRules?: RuleFilterSet;
}

export abstract class LinterBase {
Expand Down Expand Up @@ -222,47 +223,20 @@ export class Evaluation<T> {
* Evaluates whether the rule should be evaluated based on the filters applied.
*/
private shouldEvaluate(code: string, scope: string) {
if (!this.options.include || this.options.include.length === 0) {
if (!this.options.includeRules || this.options.includeRules.isEmpty()) {
return true;
}

for (const include of this.options.include) {
// match include
if (matchRule(include)) {
for (const exclude of this.options.exclude || []) {
// match exclude
if (matchRule(exclude)) {
return false;
}
}
return true;
if (this.options.includeRules.matches(code, scope)) {
if (this.options.excludeRules?.matches(code, scope)) {
return false;
}
return true;
}

return false;

function matchRule(filter: string) {
if (filter.indexOf(':') === -1) {
filter += ':*'; // add "*" scope filter if there isn't one
}

// filter format is "code:scope" and both support "*" suffix to indicate startsWith
const [codeFilter, scopeFilter] = filter.split(':');
return matchPattern(code, codeFilter) && matchPattern(scope, scopeFilter);
}

function matchPattern(s: string, pattern: string) {
if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
return s.startsWith(prefix);
} else {
return s === pattern;
}
}
}

}

export interface Rule {
code: string,
message: string;
Expand Down
110 changes: 110 additions & 0 deletions packages/awslint/lib/rule-specs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* Caches the list of rule filters that are specified in the include and exclude options in awslint.json.
*/
export class RuleFilterSet {
/**
* Rule filter format is code:scope e.g. 'docs-public-apis:aws-cdk-lib.TagType'.
* Wildcards can be used in the following ways:
*
* 1. Match any code e.g.: '*:aws-cdk-lib.TagType'
* 2. Match any scope e.g.: 'docs-public-apis:*'
* 3. Match any scope prefix e.g.: 'docs-public-apis:aws-cdk-lib.Tag*'
*/

private codesToScopes = new Map<string, Set<string>>(); // code -> list of scopes

private codesToScopePrefixes = new Map<string, Set<string>>(); // code -> list of scope prefixes (wildcards)

private scopes = new Set<string>(); // list of scopes with a wilcard code

private scopePrefixes = new Set<string>(); // list of scope prefixes with a wildcard code

private _isEmpty: boolean = false;

constructor(ruleFilterList: string[]) {
if (!ruleFilterList || ruleFilterList.length == 0) {
this._isEmpty = true;
}

for (var filter of ruleFilterList) {
if (filter.indexOf(':') === -1) {
filter += ':*'; // add "*" scope filter if there isn't one
}

const [code, scope] = filter.split(':');

if (this.hasWildcard(code)) {
if (code.length > 1) {
throw new Error(`Error parsing rule filter ${filter}: rule code can only be a single wildcard, or a complete string.`);
} else {
if (this.hasWildcard(scope)) {
this.scopePrefixes.add(scope);
} else {
this.scopes.add(scope);
}
}
} else {
if (this.hasWildcard(scope)) {
this.addToMap(this.codesToScopePrefixes, code, scope);
} else {
this.addToMap(this.codesToScopes, code, scope);
}
}
}
}

public matches(code: string, scope: string) {
// Check complete filter
const completeScopes = this.codesToScopes.get(code);
if (completeScopes && completeScopes.has(scope)) {
return true;
}

// Check if scope matches a prefix e.g. 'docs-public-apis:aws-cdk-lib.Tag*'
const scopePrefixesForCode = this.codesToScopePrefixes.get(code);
if (scopePrefixesForCode) {
if (this.containsPrefix(scopePrefixesForCode, scope)) {
return true;
}
}

// Check if scope has a wildcard code e.g. '*:aws-cdk-lib.TagType'
if (this.scopes.has(scope)) {
return true;
}

// Check if scope matches a prefix with a wildcard code e.g. '*:aws-cdk-lib.TagType*'
if (this.containsPrefix(this.scopePrefixes, scope)) {
return true;
}

return false;
}

private containsPrefix(prefixes: Set<string>, value: string) {
for (const prefix of prefixes) {
const prefixStr = prefix.slice(0, -1); // Strip the asterisk
if (value.startsWith(prefixStr)) {
return true;
}
}

return false;
}

public isEmpty() {
return this._isEmpty;
}

private addToMap(map: Map<string, Set<string>>, code: string, scope: string) {
if (!map.has(code)) {
map.set(code, new Set<string>());
}

map.get(code)?.add(scope);
}

private hasWildcard(value: string) {
return value.indexOf('*') !== -1;
}
}

0 comments on commit a4d1cee

Please sign in to comment.