Ok, I figured out how to get what I needed.
As you know, the issue is I needed to restrict the type of the RawRepresentable, which requires a generic/associated type, but if you use that, then you can't define a variable as an array of that type.
But then I asked myself why did I need that. The answer is because I'm using that RawRepresentable:String to build up another collection of CommandDefinition objects, and it's that value that I'm really interested in. So, the solution was to use a second level of protocols with that second level having the associated type to satisfy the requirements of the first level (base) protocol which can't have them.
Here's the above re-written, with the missing pieces of the puzzle added.
First, the reusable framework that can be added to any extension project as-is. Its comprised of the CommandSetBase, CommandSet, ExtensionBase and an extension on CommandSet:
typealias CommandDefinition = [XCSourceEditorCommandDefinitionKey: Any]
protocol CommandSetBase : XCSourceEditorCommand {
static var commandDefinitions : [CommandDefinition] { get }
}
protocol CommandSet : CommandSetBase {
associatedtype Command : RawRepresentable where Command.RawValue == String
static var commands:[Command] { get }
}
class ExtensionBase : NSObject, XCSourceEditorExtension {
var commandSets:[CommandSetBase.Type]{
return []
}
final var commandDefinitions: [CommandDefinition] {
return commandSets.flatMap{ commandSet in commandSet.commandDefinitions }
}
}
Here's the extension for CommandSet that uses the CommandSet-defined 'commands' associated type to satisfy the CommandSetBase requirement of the commandDefinitions (this was the missing piece):
extension CommandSet {
static var commandDefinitions:[CommandDefinition] {
return commands.map({
command in
return [
XCSourceEditorCommandDefinitionKey.classNameKey : String(reflecting:self),
XCSourceEditorCommandDefinitionKey.identifierKey : String(describing:command),
XCSourceEditorCommandDefinitionKey.nameKey : command.rawValue
]
})
}
}
And here's the app-specific implementation of the command sets and the extension that uses them.
First, the extension itself...
class Extension : ExtensionBase {
override var commandSets:[CommandSetBase.Type]{
return [
NavigationCommands.self,
SelectionCommands.self
]
}
func extensionDidFinishLaunching() {
}
}
Now the Selection commands:
class SelectionCommands: NSObject, CommandSet {
enum Command : String {
case align = "Align"
case alignWithOptions = "Align with options..."
}
static let commands = [
Command.align,
Command.alignWithOptions
]
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
print("You executed the Selection command \(invocation.commandIdentifier)")
completionHandler(nil)
}
}
And lastly, the Navigation commands:
class NavigationCommands : NSObject, CommandSet {
enum Command : String {
case jumpTo = "Jump to..."
case goBack = "Go back"
}
static let commands = [
Command.jumpTo,
Command.goBack
]
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
print("You executed the Navigation command \(invocation.commandIdentifier)")
completionHandler(nil)
}
}
And here's the result...

If Swift ever allows you to enumerate the cases of an enum, then I could eliminate the seemingly-redundant 'static let commands' in the CommandSets above.