private ArrayList<Diagnostic> handleFieldReference(SchemaNode identifierNode) {
        ArrayList<Diagnostic> ret = new ArrayList<>();

        Node parent = identifierNode.getParent();

        // Edge case: if we are in a summary element and there is specified a source list, we will not mark it as a reference
        if (parent.isASTInstance(summaryInDocument.class) && summaryHasSourceList(parent)) {
            return ret;
        }

        // Another edge case: if someone writes summary documentid {}
        if ((parent.isASTInstance(summaryInDocument.class) || parent.isASTInstance(summarySourceList.class))
            && identifierNode.getText().equals("documentid")) {
            /*
             * TODO: this actually doesn't work when you deploy if parent is summarySourceList and 
             *       you have imported a field for some reason. It would be helpful to show a nice message to the user.
             */
            Optional<Symbol> scope = CSTUtils.findScope(identifierNode);
            if (scope.isPresent()) {
                identifierNode.setSymbol(SymbolType.FIELD, context.fileURI(), scope.get());
            } else {
                identifierNode.setSymbol(SymbolType.FIELD, context.fileURI());
            }
            identifierNode.setSymbolStatus(SymbolStatus.BUILTIN_REFERENCE);
            return ret;
        }

        // Another edge case: rank-type ...
        if ((parent.isASTInstance(fieldRankType.class) || parent.isASTInstance(rankTypeElm.class)) 
                && identifierNode.getPreviousSibling() != null  
                && identifierNode.getPreviousSibling().isASTInstance(COLON.class)) {
            List<String> validRankTypes = new ArrayList<>() {{
                add("identity");
                add("about");
                add("tags");
                add("empty");
                add("default"); // sneaky valid rank-type
            }};

            if (!validRankTypes.contains(identifierNode.getText())) {
                String msg = "Invalid rank type. Supported rank types are ";
                for (int i = 0; i < validRankTypes.size(); ++i) {
                    msg += "'" + validRankTypes.get(i) + "'";
                    if (i < validRankTypes.size() - 1) msg += ", ";
                }
                msg += ".";
                ret.add(new SchemaDiagnostic.Builder()
                    .setRange(identifierNode.getRange())
                    .setMessage(msg)
                    .setSeverity(DiagnosticSeverity.Error)
                    .build());
            }
            return ret;
        }

        String fieldIdentifier = identifierNode.getText();

        String[] subfields = fieldIdentifier.split("[.]");

        int newStart = identifierNode.getRange().getStart().getCharacter();

        // First item in the list should be of type field
        int newEnd = newStart + subfields[0].length();

        identifierNode.setNewEndCharacter(newEnd);

        if (identifierNode.size() != 0) {
            identifierNode.get(0).getSchemaNode().setNewEndCharacter(newEnd);
        }

        Optional<Symbol> scope = CSTUtils.findScope(identifierNode);

        SymbolType firstType = SymbolType.FIELD;

        if (parent.isASTInstance(structFieldElm.class)) {
            firstType = SymbolType.SUBFIELD;
        }

        if (scope.isPresent()) {
            identifierNode.setSymbol(firstType, context.fileURI(), scope.get());
        } else {
            identifierNode.setSymbol(firstType, context.fileURI());
        }
        identifierNode.setSymbolStatus(SymbolStatus.UNRESOLVED);

        int myIndex = parent.indexOf(identifierNode);
        for (int i = 1; i < subfields.length; ++i) {
            newStart += subfields[i-1].length() + 1; // +1 for the dot
            newEnd += subfields[i].length() + 1;

            identifierStr newASTNode = new identifierStr();
            newASTNode.setTokenSource(identifierNode.getTokenSource());
            newASTNode.setBeginOffset(identifierNode.getOriginalSchemaNode().getBeginOffset());
            newASTNode.setEndOffset(identifierNode.getOriginalSchemaNode().getEndOffset());

            SchemaNode newNode = new SchemaNode(newASTNode);
            newNode.setNewStartCharacter(newStart);
            newNode.setNewEndCharacter(newEnd);
            parent.insertChildAfter(myIndex, newNode);

            scope = CSTUtils.findScope(newNode);

            if (scope.isPresent()) {
                newNode.setSymbol(SymbolType.SUBFIELD, context.fileURI(), scope.get());
            } else {
                newNode.setSymbolStatus(SymbolStatus.UNRESOLVED);
            }

            myIndex++;
        }

        return ret;
    }
If the parent of an identifier belongs to one of these classes
it will be handled by the {@link IdentifySymbolReferences#handleFieldReference} method.

This involves splitting the identifierStr node into several nodes based on the dot-syntax for fields.
integration/schema-language-server/language-server/src/main/java/ai/vespa/schemals/schemadocument/parser/schema/IdentifySymbolReferences.java