or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-nodes.mdcpd.mdindex.mdlanguage-parsing.mdrule-development.mdvisitor-pattern.md

rule-development.mddocs/

0

# Rule Development Framework

1

2

Base classes and infrastructure for developing custom PMD rules for Scala code analysis, including rule violation factories and chain visitors for efficient processing. This framework enables creation of static analysis rules specifically tailored for Scala language constructs.

3

4

## Capabilities

5

6

### Base Scala Rule Class

7

8

Foundation class for implementing custom PMD rules for Scala code analysis.

9

10

```java { .api }

11

/**

12

* The default base implementation of a PMD Rule for Scala. Uses the Visitor

13

* Pattern to traverse the AST.

14

*/

15

public class ScalaRule extends AbstractRule implements ScalaParserVisitor<RuleContext, RuleContext> {

16

/**

17

* Create a new Scala Rule

18

* Automatically registers with Scala language module

19

*/

20

public ScalaRule();

21

22

/**

23

* Apply this rule to a collection of AST nodes

24

* @param nodes the nodes to analyze

25

* @param ctx the rule context for reporting violations

26

*/

27

@Override

28

public void apply(List<? extends Node> nodes, RuleContext ctx);

29

30

/**

31

* Generic visit method for any Scala node

32

* Default implementation visits all children recursively

33

* @param node the node to visit

34

* @param data the rule context

35

* @return the rule context

36

*/

37

@Override

38

public RuleContext visit(ScalaNode<?> node, RuleContext data);

39

40

/**

41

* Visit the root source node

42

* @param node the source root node

43

* @param data the rule context

44

* @return the rule context

45

*/

46

@Override

47

public RuleContext visit(ASTSource node, RuleContext data);

48

49

// All ScalaParserVisitor methods are implemented with default delegation

50

// Override specific methods to implement rule logic for particular node types

51

52

@Override

53

public RuleContext visit(ASTDefnClass node, RuleContext data);

54

@Override

55

public RuleContext visit(ASTDefnDef node, RuleContext data);

56

@Override

57

public RuleContext visit(ASTTermApply node, RuleContext data);

58

// ... and 100+ other visit methods for all AST node types

59

}

60

```

61

62

**Usage Examples:**

63

64

```java

65

// Simple rule checking for long method names

66

public class LongMethodNameRule extends ScalaRule {

67

private static final int MAX_METHOD_NAME_LENGTH = 30;

68

69

@Override

70

public RuleContext visit(ASTDefnDef node, RuleContext ctx) {

71

// Extract method name from the AST node

72

String methodName = extractMethodName(node);

73

74

if (methodName.length() > MAX_METHOD_NAME_LENGTH) {

75

addViolation(ctx, node,

76

"Method name '" + methodName + "' is too long (" +

77

methodName.length() + " characters, max " + MAX_METHOD_NAME_LENGTH + ")");

78

}

79

80

// Continue visiting children

81

return super.visit(node, ctx);

82

}

83

84

private String extractMethodName(ASTDefnDef methodNode) {

85

// Implementation to extract method name from AST

86

// This would traverse the node structure to find the name

87

return "extractedName"; // Simplified

88

}

89

}

90

91

// Rule checking for unused imports

92

public class UnusedImportRule extends ScalaRule {

93

private Set<String> importedNames = new HashSet<>();

94

private Set<String> usedNames = new HashSet<>();

95

96

@Override

97

public RuleContext visit(ASTSource node, RuleContext ctx) {

98

// Reset for each source file

99

importedNames.clear();

100

usedNames.clear();

101

102

// Visit entire tree

103

RuleContext result = super.visit(node, ctx);

104

105

// Report unused imports

106

for (String importedName : importedNames) {

107

if (!usedNames.contains(importedName)) {

108

addViolation(ctx, node, "Unused import: " + importedName);

109

}

110

}

111

112

return result;

113

}

114

115

@Override

116

public RuleContext visit(ASTImport node, RuleContext ctx) {

117

// Collect imported names

118

String importName = extractImportName(node);

119

importedNames.add(importName);

120

121

return super.visit(node, ctx);

122

}

123

124

@Override

125

public RuleContext visit(ASTTermName node, RuleContext ctx) {

126

// Track name usage

127

String name = extractTermName(node);

128

usedNames.add(name);

129

130

return super.visit(node, ctx);

131

}

132

133

private String extractImportName(ASTImport importNode) {

134

// Extract import name from AST structure

135

return "importedName"; // Simplified

136

}

137

138

private String extractTermName(ASTTermName nameNode) {

139

// Extract term name from AST structure

140

return "termName"; // Simplified

141

}

142

}

143

```

144

145

### Rule Violation Factory

146

147

Factory for creating rule violations with proper Scala-specific formatting and positioning.

148

149

```java { .api }

150

/**

151

* Factory for creating rule violations for Scala rules

152

*/

153

public class ScalaRuleViolationFactory implements RuleViolationFactory {

154

/**

155

* Singleton instance of the factory

156

*/

157

public static final ScalaRuleViolationFactory INSTANCE;

158

159

/**

160

* Create a rule violation for a Scala AST node

161

* @param rule the rule that was violated

162

* @param ctx the rule context

163

* @param node the AST node where violation occurred

164

* @param message the violation message

165

* @return created RuleViolation instance

166

*/

167

@Override

168

public RuleViolation createRuleViolation(Rule rule, RuleContext ctx, Node node, String message);

169

170

/**

171

* Create a rule violation with additional details

172

* @param rule the rule that was violated

173

* @param ctx the rule context

174

* @param node the AST node where violation occurred

175

* @param message the violation message

176

* @param beginLine beginning line number

177

* @param endLine ending line number

178

* @return created RuleViolation instance

179

*/

180

@Override

181

public RuleViolation createRuleViolation(Rule rule, RuleContext ctx, Node node, String message,

182

int beginLine, int endLine);

183

}

184

```

185

186

**Usage Examples:**

187

188

```java

189

public class CustomScalaRule extends ScalaRule {

190

@Override

191

public RuleContext visit(ASTDefnClass node, RuleContext ctx) {

192

if (violatesRule(node)) {

193

// Use the factory to create properly formatted violations

194

RuleViolation violation = ScalaRuleViolationFactory.INSTANCE

195

.createRuleViolation(this, ctx, node, "Custom rule violation message");

196

197

ctx.getReport().addRuleViolation(violation);

198

}

199

200

return super.visit(node, ctx);

201

}

202

203

private boolean violatesRule(ASTDefnClass classNode) {

204

// Custom rule logic

205

return true;

206

}

207

}

208

```

209

210

### Rule Chain Visitor

211

212

Optimized visitor for processing multiple rules efficiently in a single AST traversal.

213

214

```java { .api }

215

/**

216

* Rule chain visitor for efficient rule processing

217

* Allows multiple rules to be applied in a single AST traversal

218

*/

219

public class ScalaRuleChainVisitor implements ScalaParserVisitor<RuleContext, RuleContext> {

220

/**

221

* Create rule chain visitor with collection of rules

222

* @param rules the rules to apply during traversal

223

*/

224

public ScalaRuleChainVisitor(Collection<ScalaRule> rules);

225

226

/**

227

* Add a rule to the chain

228

* @param rule the rule to add

229

*/

230

public void addRule(ScalaRule rule);

231

232

/**

233

* Remove a rule from the chain

234

* @param rule the rule to remove

235

*/

236

public void removeRule(ScalaRule rule);

237

238

/**

239

* Visit node with all rules in the chain

240

* @param node the node to visit

241

* @param ctx the rule context

242

* @return the rule context

243

*/

244

@Override

245

public RuleContext visit(ScalaNode<?> node, RuleContext ctx);

246

247

// Implements all visitor methods to apply rule chain

248

}

249

```

250

251

**Usage Examples:**

252

253

```java

254

// Create multiple rules

255

ScalaRule rule1 = new LongMethodNameRule();

256

ScalaRule rule2 = new UnusedImportRule();

257

ScalaRule rule3 = new ComplexityRule();

258

259

// Create chain visitor for efficient processing

260

Collection<ScalaRule> rules = Arrays.asList(rule1, rule2, rule3);

261

ScalaRuleChainVisitor chainVisitor = new ScalaRuleChainVisitor(rules);

262

263

// Apply all rules in single traversal

264

ASTSource ast = parser.parse("Example.scala", sourceReader);

265

RuleContext ctx = new RuleContext();

266

ast.accept(chainVisitor, ctx);

267

268

// All violations from all rules are now in the context

269

```

270

271

### Advanced Rule Patterns

272

273

Common patterns for implementing sophisticated Scala rules.

274

275

**Pattern 1: Multi-Pass Analysis**

276

277

```java

278

public class ComplexAnalysisRule extends ScalaRule {

279

private Map<String, ASTDefnClass> classes = new HashMap<>();

280

private Map<String, List<ASTDefnDef>> classMethods = new HashMap<>();

281

282

@Override

283

public RuleContext visit(ASTSource node, RuleContext ctx) {

284

// First pass: collect all classes and methods

285

collectClassesAndMethods(node);

286

287

// Second pass: analyze relationships

288

analyzeClassRelationships(ctx);

289

290

return super.visit(node, ctx);

291

}

292

293

private void collectClassesAndMethods(ASTSource root) {

294

root.accept(new ScalaParserVisitorAdapter() {

295

@Override

296

public Object visit(ASTDefnClass node, Object data) {

297

String className = extractClassName(node);

298

classes.put(className, node);

299

classMethods.put(className, new ArrayList<>());

300

return super.visit(node, data);

301

}

302

303

@Override

304

public Object visit(ASTDefnDef node, Object data) {

305

String currentClass = getCurrentClass(node);

306

if (currentClass != null) {

307

classMethods.get(currentClass).add(node);

308

}

309

return super.visit(node, data);

310

}

311

}, null);

312

}

313

314

private void analyzeClassRelationships(RuleContext ctx) {

315

// Complex analysis using collected data

316

for (Map.Entry<String, ASTDefnClass> entry : classes.entrySet()) {

317

String className = entry.getKey();

318

ASTDefnClass classNode = entry.getValue();

319

List<ASTDefnDef> methods = classMethods.get(className);

320

321

if (methods.size() > 20) {

322

addViolation(ctx, classNode,

323

"Class '" + className + "' has too many methods (" + methods.size() + ")");

324

}

325

}

326

}

327

328

private String extractClassName(ASTDefnClass node) { return "ClassName"; }

329

private String getCurrentClass(ASTDefnDef node) { return "CurrentClass"; }

330

}

331

```

332

333

**Pattern 2: Context-Aware Rules**

334

335

```java

336

public class ContextAwareRule extends ScalaRule {

337

private Stack<String> contextStack = new Stack<>();

338

339

@Override

340

public RuleContext visit(ASTDefnClass node, RuleContext ctx) {

341

String className = extractClassName(node);

342

contextStack.push("class:" + className);

343

344

try {

345

return super.visit(node, ctx);

346

} finally {

347

contextStack.pop();

348

}

349

}

350

351

@Override

352

public RuleContext visit(ASTDefnDef node, RuleContext ctx) {

353

String methodName = extractMethodName(node);

354

contextStack.push("method:" + methodName);

355

356

try {

357

// Rule logic can access full context

358

String currentContext = String.join(" -> ", contextStack);

359

360

if (violatesRule(node, currentContext)) {

361

addViolation(ctx, node, "Violation in context: " + currentContext);

362

}

363

364

return super.visit(node, ctx);

365

} finally {

366

contextStack.pop();

367

}

368

}

369

370

private boolean violatesRule(ASTDefnDef node, String context) {

371

// Context-sensitive rule logic

372

return context.contains("class:TestClass") &&

373

extractMethodName(node).startsWith("test");

374

}

375

376

private String extractClassName(ASTDefnClass node) { return "ClassName"; }

377

private String extractMethodName(ASTDefnDef node) { return "methodName"; }

378

}

379

```

380

381

**Pattern 3: Configuration-Based Rules**

382

383

```java

384

public class ConfigurableRule extends ScalaRule {

385

private int maxComplexity = 10;

386

private boolean checkPrivateMethods = false;

387

private Set<String> excludedPatterns = new HashSet<>();

388

389

public ConfigurableRule() {

390

// Load configuration from rule properties

391

loadConfiguration();

392

}

393

394

@Override

395

public RuleContext visit(ASTDefnDef node, RuleContext ctx) {

396

String methodName = extractMethodName(node);

397

398

// Skip excluded patterns

399

if (excludedPatterns.stream().anyMatch(methodName::matches)) {

400

return super.visit(node, ctx);

401

}

402

403

// Skip private methods if not configured to check them

404

if (!checkPrivateMethods && isPrivate(node)) {

405

return super.visit(node, ctx);

406

}

407

408

int complexity = calculateComplexity(node);

409

if (complexity > maxComplexity) {

410

addViolation(ctx, node,

411

"Method complexity (" + complexity + ") exceeds maximum (" + maxComplexity + ")");

412

}

413

414

return super.visit(node, ctx);

415

}

416

417

private void loadConfiguration() {

418

// Load from rule properties

419

maxComplexity = getIntProperty("maxComplexity", 10);

420

checkPrivateMethods = getBooleanProperty("checkPrivateMethods", false);

421

String excludePattern = getStringProperty("excludePatterns", "");

422

if (!excludePattern.isEmpty()) {

423

excludedPatterns.addAll(Arrays.asList(excludePattern.split(",")));

424

}

425

}

426

427

private String extractMethodName(ASTDefnDef node) { return "methodName"; }

428

private boolean isPrivate(ASTDefnDef node) { return false; }

429

private int calculateComplexity(ASTDefnDef node) { return 5; }

430

}

431

```

432

433

### Testing Scala Rules

434

435

Framework support for testing custom rules:

436

437

```java

438

// Example test class for Scala rules

439

public class ScalaRuleTest {

440

@Test

441

public void testLongMethodNameRule() {

442

ScalaRule rule = new LongMethodNameRule();

443

444

String scalaCode = """

445

object TestObject {

446

def shortName(): Unit = {}

447

def thisIsAVeryLongMethodNameThatExceedsTheLimit(): Unit = {}

448

}

449

""";

450

451

// Parse and apply rule

452

ASTSource ast = parseScala(scalaCode);

453

RuleContext ctx = new RuleContext();

454

ast.accept((ScalaParserVisitor<RuleContext, RuleContext>) rule, ctx);

455

456

// Verify violations

457

List<RuleViolation> violations = ctx.getReport().getViolations();

458

assertEquals(1, violations.size());

459

assertTrue(violations.get(0).getDescription().contains("too long"));

460

}

461

462

private ASTSource parseScala(String code) {

463

// Helper method to parse Scala code for testing

464

// Implementation would use ScalaParser

465

return null; // Simplified

466

}

467

}

468

```