class

Analyzer::Groovy::Grails

Inherits Analyzer < FileHelper < Reference < Object

Grails follows a convention-over-configuration layout where each controller class under grails-app/controllers/ exposes its action methods as URL endpoints. The default URL mapping is "/$controller/$action?/$id?(.$format)?", so we surface every action method as /<controller>/<action> (the controller name is the class name with the Controller suffix dropped and the first letter lowercased).

Two action styles are handled:

  • Method form: def show() { ... }
  • Closure form: def show = { ... } (legacy Grails)

When the controller declares static allowedMethods = [save: 'POST', update: ['PUT', 'PATCH']] those restrictions are honored; otherwise actions are emitted as GET (the default Grails dispatch verb when no restriction is set).

UrlMappings.groovy is also scanned for explicit string-based mappings of the form get '/api/users'(controller: 'user', action: 'list') which are surfaced as additional endpoints. It lives under grails-app/conf/ on Grails 1.x/2.x and grails-app/controllers/ on Grails 3+; both locations are handled. Per-verb action = [GET: ..., PUT: ...] dispatch maps and ${name} GString path variables are recognized, while bare-status-code ("404", "500") error mappings are excluded.

Constants

BODY_ARGS_KEY_RE = Regex.union("controller:", "action:", "view:", "uri:")

One precompiled Regex.union scan (PCRE2 JIT) replaces the four OR-ed String#includes? scans that used to gate every verbless paren-form mapping match in emit_mapping_endpoints (runs once per match, so a UrlMappings file with many entries repeats this often). union auto-escapes each literal, so this is exactly equivalent to the OR-chain it replaces.

CLOSURE_MAPPING_HEADER = /^[ \t]*(['"])([^'"]+)\1\s*\{/m

Opening of a closure-form mapping, '/path' {; the body is brace-matched from the { (see emit_mapping_endpoints).

DEF_ACTION_RE = /\G(public\s+|protected\s+|private\s+)?def\s+([a-z_][A-Za-z0-9_]*)\s*(?:\(|=\s*(?=\{))/

\G: anchored at the byte offset passed to match_at_byte_index. Matched with NO_UTF_CHECK, or PCRE2 revalidates the whole body per call; the body is built char by char, so it is valid UTF-8.

DEFAULT_METHODS = ["GET"]
HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"] of ::String
PRIMITIVE_ACTION_RE = /\G((?:(?:public|protected|private|static|final|synchronized)\s+)*)(void|int|long|boolean|byte|short|float|double|char)\s+([a-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*(?:throws\s+[A-Za-z0-9_,\s.]+)?\s*\{/
RESOURCES_ENDPOINTS = [{suffix: "", method: "GET"}, {suffix: "", method: "POST"}, {suffix: "/:id", method: "GET"}, {suffix: "/:id", method: "PUT"}, {suffix: "/:id", method: "PATCH"}, {suffix: "/:id", method: "DELETE"}]

Endpoints generated by (resources: "name") shortcuts in UrlMappings.groovy. Each tuple is suffix appended to the base URL plus the verb.

RESTFUL_ACTIONS = [{name: "index", method: "GET"}, {name: "show", method: "GET"}, {name: "save", method: "POST"}, {name: "update", method: "PUT"}, {name: "patch", method: "PATCH"}, {name: "delete", method: "DELETE"}]

Actions inherited from RestfulController<T> (Grails REST base).

SCAFFOLD_ACTIONS = [{name: "index", method: "GET"}, {name: "show", method: "GET"}, {name: "create", method: "GET"}, {name: "save", method: "POST"}, {name: "edit", method: "GET"}, {name: "update", method: "PUT"}, {name: "delete", method: "DELETE"}]

Actions added implicitly by static scaffold = X (Grails legacy CRUD scaffolding).

SKIP_ACTION_NAMES = ["beforeInterceptor", "afterInterceptor", "afterView", "allowedMethods", "scaffold", "defaultAction", "errors", "response", "request", "params", "responseFormats", "namespace", "transactional"] of ::String
TYPED_ACTION_RE = /\G((?:(?:public|protected|private|static|final|synchronized)\s+)*)([A-Z][A-Za-z0-9_]*(?:\s*<[^<>]+>)?(?:\s*\[\s*\])?)\s+([a-z_][A-Za-z0-9_]*)\s*\([^)]*\)\s*(?:throws\s+[A-Za-z0-9_,\s.]+)?\s*\{/
URL_MAPPINGS_DIR_RE = Regex.union("/grails-app/conf/", "/grails-app/controllers/")

One precompiled Regex.union scan (PCRE2 JIT) replaces the two OR-ed String#includes? scans url_mappings_path? used to run over every candidate path -- Crystal's includes? is not Boyer-Moore accelerated, so a single regex pass is cheaper than two substring scans. union auto-escapes each literal, so this is exactly equivalent to the OR-chain it replaces.

Class methods

tech_name
Source

Instance methods

analyze
Source
tech

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.

Source

Nested types