Changing class prefixes

The standard approach to handling class name clashes (as Pharo has no namespaces) is to use short prefixes, typically the initials of the project: OP for OpenPonk, PP for PetitParser, etc.

Prefixes for XMI Analyzer

But what if you don’t like your prefix, or it happens to clash with something?

Well, you could go over each class and rename it by hand… or you use dedicated code rewrite library — which as you’ll (hopefully) see is actually pretty easy to use.

The most direct approach is to just tell the class to rename itself.

1
Something rename: 'NewSomething'

Although this works, it does just that — renames the class.

Class rename without references

What we actually want is to rename not just the class, but also all the references to it in the system.

RB Refactoring library

Doing larger-scale rewrites is rarely a good idea. It is too easy to forget some code or introduce a bug. Plus it’s a lot of work. Instead a dedicated rewrite library is available in the Refactoring-Core package.

This library is also used under the hood by various system tools such as System Browser.

The Refactoring library doesn’t just do the rewrites, but for each rewrite rule performs a range of preconditions checks (new name already exists, method we are trying to remove exists, etc.). Furthermore it offers you a changeset for a review that you can confirm or cancel.

I was told that there used to be documentation for this library, but until I/someone manages to find it, the Refactoring-Tests-Core is the best bet — over 200 tests with nice self-explanatory examples.

Refactoring basics

As there is no docs for now, I’ll try to give a dead-simple overview.

There is a class for each change you might be interested, such as RBRenameClassRefactoring to rename a class, RBAddMethodRefactoring to add a new method, etc.

Such classes on their class-side offer a convenience methods: RBAddMethodRefactoring>>addMethod:toClass:inProtocols, RBRenameClassRefactoring>>rename:to:, RBRenameClassRefactoring>>model:rename:to:.

What is model in the last one? Model is an instance of RBNamespace which allows you to group a bunch of rewrites into one set. So instead of confirming change for every individual rule you will see a grouped changeset (where you can still pick what you want).

1
rule := RBRenameClassRefactoring rename: Something to: 'NewSomething'.

Once we have a rule we can execute it, which will perform the checks and immediately rewrite the code .

1
rule execute.

Or we can just perform the preconditions and prepare the transformation.

1
rule primitiveExecute.

That way we can view the changes and decide whether we like them or not.

1
(ChangesBrowser changes: rule changes changes) open

Put together, we can have something like this:

Playing around with rewrites
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"Execute line-by-line, not all at once!"
"Create a new class to play around with."
Object subclass: #Something.
"Create a rewrite rule and immediately execute."
(RBRenameClassRefactoring rename: Something to: 'NewSomething') execute.
"Create a rewrite rule"
rule := (RBRenameClassRefactoring rename: NewSomething to: 'Something').
"Perform prechecks & prepare transformation"
rule primitiveExecute.
"View the changes"
(ChangesBrowser changes: rule changes changes) open.

Changing prefixes

After the brief overview we can get back to what we are actually after — script for changing the class prefixes, but now we have enough knowledge to put it all together.

Changing prefix of all classes in a package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"select the target package"
pkg := RPackage organizer packageNamed: 'XMI-Analyzer'.
"we want to group the changes, so let's use a single model"
model := RBNamespace new.
"for each class prepare the transformation"
pkg classes do: [ :each | |rule|
(RBRenameClassRefactoring
model: model
rename: each
to: 'XA', (each name withoutPrefix: 'XMI'))
primitiveExecute.
].
"and view the changes"
(ChangesBrowser changes: model changes changes) open.
Reviewing the changes

Confirm (or cancel) the changes, and see it take effect.

Rewritten code

And that’s it. If you know what you are doing you can perform even large-scale rewrites safely with just couple of lines of code. Getting more and more familiar with the system has a snowballing effect, so don’t hesitate to dig around.