This is an automated email from the ASF dual-hosted git repository.

Harbs pushed a commit to branch codegraph
in repository https://gitbox.apache.org/repos/asf/royale-asjs.git

commit cc94d840b773a817559dd56bf9da4f82afee4495
Author: Harbs <[email protected]>
AuthorDate: Sat Aug 1 22:09:03 2026 +0300

    Add code graph generation and validation targets, scripts, and configuration
---
 frameworks/build.xml                               |  24 +++
 frameworks/codegraph-index-metadata.xsl            |  89 ++++++++++
 frameworks/codegraphs.xml                          | 167 ++++++++++++++++++
 frameworks/codegraphs.xsl                          | 113 ++++++++++++
 .../src/main/config/compile-js-config.xml          |   1 +
 frameworks/projects/pom.xml                        |  23 +++
 frameworks/scripts/generate-codegraph-index.js     | 189 +++++++++++++++++++++
 frameworks/scripts/validate-codegraphs.js          | 155 +++++++++++++++++
 8 files changed, 761 insertions(+)

diff --git a/frameworks/build.xml b/frameworks/build.xml
index bd3c46c027..133acaa08a 100644
--- a/frameworks/build.xml
+++ b/frameworks/build.xml
@@ -160,6 +160,30 @@
                <!-- <antcall target="ExternsJS"/> -->
     </target>
 
+        <target name="codegraphs" description="Generates public API code 
graphs for all framework modules">
+                <ant antfile="${basedir}/codegraphs.xml" target="codegraphs" 
inheritAll="true"/>
+        </target>
+
+        <target name="codegraph" description="Generates public API code graphs 
for one framework module">
+            <ant antfile="${basedir}/codegraphs.xml" target="codegraph" 
inheritAll="true"/>
+        </target>
+
+        <target name="validate-codegraphs" description="Validates generated 
public API code graphs">
+            <ant antfile="${basedir}/codegraphs.xml" 
target="validate-codegraphs" inheritAll="true"/>
+        </target>
+
+        <target name="validate-codegraph" description="Generates and validates 
one framework module code graph">
+            <ant antfile="${basedir}/codegraphs.xml" 
target="validate-codegraph" inheritAll="true"/>
+        </target>
+
+        <target name="validate-codegraph-determinism" description="Regenerates 
and byte-compares one framework module code graph">
+            <ant antfile="${basedir}/codegraphs.xml" 
target="validate-codegraph-determinism" inheritAll="true"/>
+        </target>
+
+        <target name="codegraph-index" description="Stages code graph shards 
and generates aggregate indexes">
+            <ant antfile="${basedir}/codegraphs.xml" target="codegraph-index" 
inheritAll="true"/>
+        </target>
+
     <target name="test" description="Tests all SWCs">
         <antcall target="LanguageTest"/>
         <antcall target="CoreTest"/>
diff --git a/frameworks/codegraph-index-metadata.xsl 
b/frameworks/codegraph-index-metadata.xsl
new file mode 100644
index 0000000000..8937b102bc
--- /dev/null
+++ b/frameworks/codegraph-index-metadata.xsl
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to You under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+
+-->
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
+    xmlns:m="http://maven.apache.org/POM/4.0.0";
+    exclude-result-prefixes="m">
+
+    <xsl:output method="text" encoding="UTF-8"/>
+
+    <xsl:template match="/m:project">
+        <xsl:for-each select="m:modules/m:module">
+            <xsl:variable name="module" select="string(.)"/>
+            <xsl:variable name="project" select="document(concat($module, 
'/pom.xml'), /)/m:project"/>
+            <xsl:text>MODULE&#9;</xsl:text>
+            <xsl:value-of select="$module"/>
+            <xsl:text>&#9;</xsl:text>
+            <xsl:value-of select="$project/m:parent/m:groupId"/>
+            <xsl:text>&#9;</xsl:text>
+            <xsl:value-of select="$project/m:artifactId"/>
+            <xsl:text>&#9;</xsl:text>
+            <xsl:value-of select="$project/m:version"/>
+            <xsl:text>&#10;</xsl:text>
+
+            <xsl:for-each select="$project/m:dependencies/m:dependency | 
$project/m:profiles/m:profile/m:dependencies/m:dependency">
+                <xsl:text>DEPENDENCY&#9;</xsl:text>
+                <xsl:value-of select="$module"/>
+                <xsl:text>&#9;</xsl:text>
+                <xsl:value-of select="m:classifier"/>
+                <xsl:text>&#9;</xsl:text>
+                <xsl:value-of select="m:groupId"/>
+                <xsl:text>&#9;</xsl:text>
+                <xsl:value-of select="m:artifactId"/>
+                <xsl:text>&#9;</xsl:text>
+                <xsl:value-of select="m:version"/>
+                <xsl:text>&#10;</xsl:text>
+            </xsl:for-each>
+
+            <xsl:for-each 
select="$project/m:build/m:plugins/m:plugin[m:artifactId = 
'royale-maven-plugin']/m:configuration/m:namespaces/m:namespace">
+                <xsl:variable name="namespaceType" select="m:type"/>
+                <xsl:variable name="uri" select="m:uri"/>
+                <xsl:variable name="manifestPath" 
select="substring-after(m:manifest, '${project.basedir}/')"/>
+                <xsl:text>NAMESPACE&#9;</xsl:text>
+                <xsl:value-of select="$module"/>
+                <xsl:text>&#9;</xsl:text>
+                <xsl:value-of select="$namespaceType"/>
+                <xsl:text>&#9;</xsl:text>
+                <xsl:value-of select="$uri"/>
+                <xsl:text>&#9;</xsl:text>
+                <xsl:value-of select="$manifestPath"/>
+                <xsl:text>&#10;</xsl:text>
+                <xsl:for-each select="document($manifestPath, 
$project)/*/component">
+                    <xsl:text>TAG&#9;</xsl:text>
+                    <xsl:value-of select="$module"/>
+                    <xsl:text>&#9;</xsl:text>
+                    <xsl:value-of select="$namespaceType"/>
+                    <xsl:text>&#9;</xsl:text>
+                    <xsl:value-of select="$uri"/>
+                    <xsl:text>&#9;</xsl:text>
+                    <xsl:value-of select="@id"/>
+                    <xsl:text>&#9;</xsl:text>
+                    <xsl:value-of select="@class"/>
+                    <xsl:text>&#9;</xsl:text>
+                    <xsl:value-of select="@lookupOnly"/>
+                    <xsl:text>&#10;</xsl:text>
+                </xsl:for-each>
+            </xsl:for-each>
+        </xsl:for-each>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/frameworks/codegraphs.xml b/frameworks/codegraphs.xml
new file mode 100644
index 0000000000..e8ae15dac2
--- /dev/null
+++ b/frameworks/codegraphs.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<project name="codegraphs" default="codegraphs" basedir=".">
+
+    <property name="ROYALE_HOME" location=".."/>
+    <property file="${ROYALE_HOME}/env.properties"/>
+    <property environment="env"/>
+    <property file="${ROYALE_HOME}/local.properties"/>
+    <property file="${ROYALE_HOME}/build.properties"/>
+
+    <condition property="ROYALE_COMPILER_HOME" value="${ROYALE_HOME}/js">
+        <available file="${ROYALE_HOME}/js/lib/codegraph.jar"/>
+    </condition>
+    <condition property="ROYALE_COMPILER_HOME" 
value="${env.ROYALE_COMPILER_HOME}">
+        <available file="${env.ROYALE_COMPILER_HOME}/lib/codegraph.jar"/>
+    </condition>
+    <condition property="AIR_HOME" value="${env.AIR_HOME}">
+        <isset property="env.AIR_HOME"/>
+    </condition>
+    <property name="AIR_HOME" value="${ROYALE_HOME}"/>
+
+    <macrodef name="codegraph-js">
+        <attribute name="module"/>
+        <attribute name="config" default="js-config.xml"/>
+        <element name="extra-args" optional="true"/>
+        <sequential>
+            <echo message="Generating JS code graph for @{module}"/>
+            <mkdir dir="${basedir}/projects/@{module}/target/codegraph/js"/>
+            <java jar="${ROYALE_COMPILER_HOME}/lib/codegraph.jar"
+                dir="${basedir}/js/projects/@{module}JS"
+                fork="true" failonerror="true">
+                <jvmarg value="-Xmx384m"/>
+                <jvmarg value="-Dsun.io.useCanonCaches=false"/>
+                <jvmarg value="-Droyalecompiler=${ROYALE_COMPILER_HOME}"/>
+                <jvmarg value="-Droyalelib=${ROYALE_HOME}/frameworks"/>
+                <arg value="-compiler.strict-xml=true"/>
+                <arg 
value="-output=${basedir}/projects/@{module}/target/codegraph/js/@{module}.json"/>
+                <arg value="-load-config=${basedir}/@{config}"/>
+                <arg 
value="-load-config+=${basedir}/js/projects/@{module}JS/src/main/config/compile-js-config.xml"/>
+                <arg value="-keep-asdoc=true"/>
+                <extra-args/>
+            </java>
+        </sequential>
+    </macrodef>
+
+    <macrodef name="codegraph-swf">
+        <attribute name="module"/>
+        <element name="extra-args" optional="true"/>
+        <sequential>
+            <echo message="Generating SWF code graph for @{module}"/>
+            <mkdir dir="${basedir}/projects/@{module}/target/codegraph/swf"/>
+            <java jar="${ROYALE_COMPILER_HOME}/lib/codegraph.jar"
+                dir="${basedir}/projects/@{module}"
+                fork="true" failonerror="true">
+                <jvmarg value="-Xmx384m"/>
+                <jvmarg value="-Dsun.io.useCanonCaches=false"/>
+                <jvmarg value="-Droyalecompiler=${ROYALE_COMPILER_HOME}"/>
+                <jvmarg value="-Droyalelib=${ROYALE_HOME}/frameworks"/>
+                <arg value="+playerglobal.version=${playerglobal.version}"/>
+                <arg value="+env.AIR_HOME=${AIR_HOME}"/>
+                <arg value="-compiler.strict-xml=true"/>
+                <arg 
value="-output=${basedir}/projects/@{module}/target/codegraph/swf/@{module}.json"/>
+                <arg 
value="-load-config=${basedir}/projects/@{module}/src/main/config/compile-swf-config.xml"/>
+                <arg value="-keep-asdoc=true"/>
+                <extra-args/>
+            </java>
+        </sequential>
+    </macrodef>
+
+    <target name="check">
+        <fail message="ROYALE_COMPILER_HOME must point to a compiler with 
lib/codegraph.jar"
+            unless="ROYALE_COMPILER_HOME"/>
+    </target>
+
+    <target name="generate-driver">
+        <mkdir dir="${basedir}/target"/>
+        <xslt in="${basedir}/projects/pom.xml"
+            out="${basedir}/target/codegraph-modules.xml"
+            style="${basedir}/codegraphs.xsl"/>
+    </target>
+
+    <target name="generate-index-metadata">
+        <mkdir dir="${basedir}/target"/>
+        <xslt in="${basedir}/projects/pom.xml"
+            out="${basedir}/target/codegraph-index-metadata.tsv"
+            style="${basedir}/codegraph-index-metadata.xsl"/>
+    </target>
+
+    <target name="codegraph-index" 
depends="validate-codegraphs,generate-index-metadata">
+        <exec executable="node" failonerror="true">
+            <arg value="${basedir}/scripts/generate-codegraph-index.js"/>
+            <arg value="--metadata"/>
+            <arg value="${basedir}/target/codegraph-index-metadata.tsv"/>
+            <arg value="--root"/>
+            <arg value="${basedir}/projects"/>
+            <arg value="--output"/>
+            <arg value="${basedir}/target/codegraphs"/>
+        </exec>
+    </target>
+
+    <target name="codegraphs" depends="check,generate-driver">
+        <ant antfile="${basedir}/target/codegraph-modules.xml" 
target="codegraphs" inheritAll="true"/>
+    </target>
+
+    <target name="codegraph" depends="check,generate-driver">
+        <fail message="Set codegraph.module to a framework module name, for 
example -Dcodegraph.module=Basic"
+            unless="codegraph.module"/>
+        <ant antfile="${basedir}/target/codegraph-modules.xml"
+            target="codegraph-${codegraph.module}" inheritAll="true"/>
+    </target>
+
+    <target name="validate-codegraphs">
+        <exec executable="node" failonerror="true">
+            <arg value="${basedir}/scripts/validate-codegraphs.js"/>
+            <arg value="--root"/>
+            <arg value="${basedir}/projects"/>
+        </exec>
+    </target>
+
+    <target name="validate-codegraph" depends="codegraph">
+        <exec executable="node" failonerror="true">
+            <arg value="${basedir}/scripts/validate-codegraphs.js"/>
+            <arg value="--root"/>
+            <arg value="${basedir}/projects"/>
+            <arg value="--module"/>
+            <arg value="${codegraph.module}"/>
+        </exec>
+    </target>
+
+    <target name="validate-codegraph-determinism">
+        <fail message="Set codegraph.module to a framework module name, for 
example -Dcodegraph.module=Basic"
+            unless="codegraph.module"/>
+        <delete dir="${basedir}/target/codegraph-baseline"/>
+        <copy todir="${basedir}/target/codegraph-baseline/${codegraph.module}">
+            <fileset dir="${basedir}/projects/${codegraph.module}" 
includes="target/codegraph/**/*.json"/>
+        </copy>
+        <antcall target="codegraph"/>
+        <exec executable="node" failonerror="true">
+            <arg value="${basedir}/scripts/validate-codegraphs.js"/>
+            <arg value="--root"/>
+            <arg value="${basedir}/projects"/>
+            <arg value="--module"/>
+            <arg value="${codegraph.module}"/>
+            <arg value="--compare-root"/>
+            <arg value="${basedir}/target/codegraph-baseline"/>
+        </exec>
+        <delete dir="${basedir}/target/codegraph-baseline"/>
+    </target>
+
+</project>
\ No newline at end of file
diff --git a/frameworks/codegraphs.xsl b/frameworks/codegraphs.xsl
new file mode 100644
index 0000000000..7e2e2776a2
--- /dev/null
+++ b/frameworks/codegraphs.xsl
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
+    xmlns:m="http://maven.apache.org/POM/4.0.0";
+    exclude-result-prefixes="m">
+
+    <xsl:output method="xml" indent="yes"/>
+
+    <xsl:template match="/">
+        <project name="codegraph-modules" default="codegraphs" basedir="..">
+            <import file="../codegraphs.xml"/>
+            <target name="codegraphs" depends="codegraphs-js,codegraphs-swf"/>
+            <target name="codegraphs-js">
+                <xsl:attribute name="depends">
+                    <xsl:for-each select="m:project/m:modules/m:module">
+                        <xsl:if test="position() != 1">,</xsl:if>
+                        <xsl:text>codegraph-</xsl:text><xsl:value-of 
select="."/><xsl:text>-js</xsl:text>
+                    </xsl:for-each>
+                </xsl:attribute>
+            </target>
+            <target name="codegraphs-swf" if="env.AIR_HOME">
+                <xsl:attribute name="depends">
+                    <xsl:for-each select="m:project/m:modules/m:module">
+                        <xsl:if test="position() != 1">,</xsl:if>
+                        <xsl:text>codegraph-</xsl:text><xsl:value-of 
select="."/><xsl:text>-swf</xsl:text>
+                    </xsl:for-each>
+                </xsl:attribute>
+            </target>
+            <xsl:for-each select="m:project/m:modules/m:module">
+                <target name="codegraph-{.}" 
depends="codegraph-{.}-js,codegraph-{.}-swf"/>
+                <target name="codegraph-{.}-js">
+                    <xsl:choose>
+                        <xsl:when test=". = 'Ace'">
+                            <codegraph-js module="{.}" 
config="ace-config.xml"/>
+                        </xsl:when>
+                        <xsl:when test=". = 'CreateJS'">
+                            <codegraph-js module="{.}" 
config="createjs-config.xml"/>
+                        </xsl:when>
+                        <xsl:when test=". = 'JQuery'">
+                            <codegraph-js module="{.}" 
config="jquery-config.xml"/>
+                        </xsl:when>
+                        <xsl:when test=". = 'MXRoyaleBase' or . = 'MXRoyale'">
+                            <codegraph-js module="{.}">
+                                <extra-args>
+                                    <arg 
value="-compiler.define+=ROYALE::DISPLAYOBJECT,IUIComponent"/>
+                                    <arg 
value="-compiler.define+=GOOG::DEBUG,goog.DEBUG"/>
+                                </extra-args>
+                            </codegraph-js>
+                        </xsl:when>
+                        <xsl:when test=". = 'SparkRoyale'">
+                            <codegraph-js module="{.}">
+                                <extra-args>
+                                    <arg 
value="-compiler.define+=GOOG::DEBUG,goog.DEBUG"/>
+                                </extra-args>
+                            </codegraph-js>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <codegraph-js module="{.}"/>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </target>
+                <target name="codegraph-{.}-swf" if="env.AIR_HOME">
+                    <xsl:choose>
+                        <xsl:when test=". = 'Ace'">
+                            <codegraph-swf module="{.}">
+                                <extra-args>
+                                    <arg 
value="-compiler.allow-subclass-overrides=true"/>
+                                </extra-args>
+                            </codegraph-swf>
+                        </xsl:when>
+                        <xsl:when test=". = 'MXRoyaleBase' or . = 'MXRoyale'">
+                            <codegraph-swf module="{.}">
+                                <extra-args>
+                                    <arg 
value="-compiler.define+=GOOG::DEBUG,true"/>
+                                    <arg 
value="-compiler.define+=ROYALE::DISPLAYOBJECT,DisplayObject"/>
+                                </extra-args>
+                            </codegraph-swf>
+                        </xsl:when>
+                        <xsl:when test=". = 'SparkRoyale'">
+                            <codegraph-swf module="{.}">
+                                <extra-args>
+                                    <arg 
value="-compiler.define+=GOOG::DEBUG,true"/>
+                                </extra-args>
+                            </codegraph-swf>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <codegraph-swf module="{.}"/>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </target>
+            </xsl:for-each>
+        </project>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git 
a/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml 
b/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml
index ab405d525c..c850783664 100644
--- a/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml
+++ b/frameworks/js/projects/SparkRoyaleJS/src/main/config/compile-js-config.xml
@@ -65,6 +65,7 @@
             <path-element>../../../../../libs/EffectsJS.swc</path-element>
             <path-element>../../../../../libs/ExpressJS.swc</path-element>
             <path-element>../../../../../libs/GraphicsJS.swc</path-element>
+            <path-element>../../../../../libs/CollectionsJS.swc</path-element>
             <path-element>../../../../../libs/DragDropJS.swc</path-element>
             <path-element>../../../../../libs/HTMLJS.swc</path-element>
             <path-element>../../../../../libs/NetworkJS.swc</path-element>
diff --git a/frameworks/projects/pom.xml b/frameworks/projects/pom.xml
index 2091f83386..2b0f370b54 100644
--- a/frameworks/projects/pom.xml
+++ b/frameworks/projects/pom.xml
@@ -122,4 +122,27 @@
     </dependency>
   </dependencies>
 
+  <profiles>
+    <profile>
+      <id>codegraphs</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.royale.compiler</groupId>
+            <artifactId>royale-maven-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>codegraphs</id>
+                <phase>prepare-package</phase>
+                <goals>
+                  <goal>compile-codegraph</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
 </project>
diff --git a/frameworks/scripts/generate-codegraph-index.js 
b/frameworks/scripts/generate-codegraph-index.js
new file mode 100644
index 0000000000..9692b98db3
--- /dev/null
+++ b/frameworks/scripts/generate-codegraph-index.js
@@ -0,0 +1,189 @@
+#!/usr/bin/env node
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const crypto = require("crypto");
+const fs = require("fs");
+const path = require("path");
+
+function parseArguments(args) {
+    const options = {
+        metadata: path.resolve(__dirname, 
"../target/codegraph-index-metadata.tsv"),
+        root: path.resolve(__dirname, "../projects"),
+        output: path.resolve(__dirname, "../target/codegraphs")
+    };
+    for (let index = 0; index < args.length; index++) {
+        const argument = args[index];
+        if (argument === "--metadata" || argument === "--root" || argument === 
"--output") {
+            options[argument.substring(2)] = path.resolve(args[++index]);
+        } else {
+            throw new Error(`Unknown argument: ${argument}`);
+        }
+    }
+    return options;
+}
+
+function readMetadata(file) {
+    const modules = new Map();
+    const lines = fs.readFileSync(file, "utf8").trim().split("\n");
+    lines.forEach((line, lineIndex) => {
+        const fields = line.split("\t");
+        const kind = fields.shift();
+        const moduleName = fields.shift();
+        if (kind === "MODULE") {
+            modules.set(moduleName, {
+                name: moduleName,
+                coordinates: {
+                    groupId: fields[0], artifactId: fields[1], version: 
fields[2], type: "swc"
+                },
+                dependencies: { js: [], swf: [] },
+                namespaces: []
+            });
+            return;
+        }
+        const module = modules.get(moduleName);
+        if (!module) {
+            throw new Error(`${file}:${lineIndex + 1}: ${kind} appears before 
MODULE ${moduleName}`);
+        }
+        if (kind === "DEPENDENCY") {
+            const classifier = fields[0];
+            if (classifier === "js" || classifier === "swf") {
+                module.dependencies[classifier].push({
+                    groupId: fields[1], artifactId: fields[2], version: 
fields[3],
+                    type: "swc", classifier
+                });
+            }
+        } else if (kind === "NAMESPACE") {
+            module.namespaces.push({ type: fields[0], uri: fields[1], 
manifest: fields[2], tags: [] });
+        } else if (kind === "TAG") {
+            const namespace = module.namespaces.find(candidate =>
+                candidate.type === fields[0] && candidate.uri === fields[1]);
+            if (!namespace) {
+                throw new Error(`${file}:${lineIndex + 1}: TAG has no matching 
namespace`);
+            }
+            namespace.tags.push({
+                name: fields[2], class: fields[3], lookupOnly: fields[4] === 
"true"
+            });
+        } else {
+            throw new Error(`${file}:${lineIndex + 1}: unknown record 
${kind}`);
+        }
+    });
+    return Array.from(modules.values());
+}
+
+function graphPath(root, module, target) {
+    return path.join(root, module, "target", "codegraph", target, 
`${module}.json`);
+}
+
+function namespaceSupportsTarget(namespace, target) {
+    return !namespace.type || namespace.type === (target === "js" ? "js" : 
"as");
+}
+
+function writeJson(file, value) {
+    fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
+}
+
+function main() {
+    const options = parseArguments(process.argv.slice(2));
+    const modules = readMetadata(options.metadata);
+    const versions = new Set(modules.map(module => 
module.coordinates.version));
+    if (versions.size !== 1 || versions.has("")) {
+        throw new Error(`Expected one non-empty framework version, found: 
${Array.from(versions).join(", ")}`);
+    }
+    const version = Array.from(versions)[0];
+    const versionRoot = path.join(options.output, version);
+    fs.rmSync(options.output, { recursive: true, force: true });
+    fs.mkdirSync(versionRoot, { recursive: true });
+
+    const symbolsByTarget = { js: new Map(), swf: new Map() };
+    const indexModules = modules.map(module => {
+        const targets = {};
+        ["js", "swf"].forEach(target => {
+            const source = graphPath(options.root, module.name, target);
+            const contents = fs.readFileSync(source);
+            const graph = JSON.parse(contents.toString("utf8"));
+            const relativePath = path.join(version, module.name, target, 
`${module.name}.json`);
+            const destination = path.join(options.output, relativePath);
+            fs.mkdirSync(path.dirname(destination), { recursive: true });
+            fs.copyFileSync(source, destination);
+            graph.symbols.forEach(symbol => 
symbolsByTarget[target].set(symbol.qualifiedName, symbol.id));
+            targets[target] = {
+                path: relativePath.split(path.sep).join("/"),
+                sha256: 
crypto.createHash("sha256").update(contents).digest("hex"),
+                symbolCount: graph.symbols.length,
+                classCount: graph.symbols.filter(symbol => symbol.kind === 
"class").length
+            };
+        });
+        return {
+            name: module.name,
+            coordinates: module.coordinates,
+            dependencies: module.dependencies,
+            targets
+        };
+    });
+
+    const mxmlTargets = {};
+    let unavailableTags = 0;
+    ["js", "swf"].forEach(target => {
+        mxmlTargets[target] = [];
+        modules.forEach(module => module.namespaces
+            .filter(namespace => namespaceSupportsTarget(namespace, target))
+            .forEach(namespace => {
+                const tags = namespace.tags.map(tag => {
+                    const id = symbolsByTarget[target].get(tag.class);
+                    if (!id) {
+                        unavailableTags++;
+                    }
+                    const mapping = {
+                        name: tag.name,
+                        class: tag.class,
+                        lookupOnly: tag.lookupOnly,
+                        available: Boolean(id)
+                    };
+                    if (id) {
+                        mapping.id = id;
+                    }
+                    return mapping;
+                });
+                mxmlTargets[target].push({
+                    module: module.name,
+                    uri: namespace.uri,
+                    manifest: namespace.manifest,
+                    tags
+                });
+            }));
+    });
+
+    writeJson(path.join(options.output, "index.json"), {
+        schemaVersion: "1.0", version, modules: indexModules
+    });
+    writeJson(path.join(options.output, "mxml.json"), {
+        schemaVersion: "1.0", version, targets: mxmlTargets
+    });
+    console.log(`Generated codegraph index for ${modules.length} module(s) at 
${options.output}.`);
+    if (unavailableTags > 0) {
+        console.log(`Preserved ${unavailableTags} MXML tag mapping(s) 
unavailable in their target graph.`);
+    }
+}
+
+try {
+    main();
+} catch (error) {
+    console.error(error.message);
+    process.exitCode = 1;
+}
\ No newline at end of file
diff --git a/frameworks/scripts/validate-codegraphs.js 
b/frameworks/scripts/validate-codegraphs.js
new file mode 100644
index 0000000000..8c14e876b2
--- /dev/null
+++ b/frameworks/scripts/validate-codegraphs.js
@@ -0,0 +1,155 @@
+#!/usr/bin/env node
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const fs = require("fs");
+const path = require("path");
+
+function parseArguments(args) {
+    const options = {
+        root: path.resolve(__dirname, "../projects"),
+        targets: ["js", "swf"]
+    };
+
+    for (let index = 0; index < args.length; index++) {
+        const argument = args[index];
+        if (argument === "--root") {
+            options.root = path.resolve(args[++index]);
+        } else if (argument === "--module") {
+            options.module = args[++index];
+        } else if (argument === "--targets") {
+            options.targets = args[++index].split(",");
+        } else if (argument === "--compare-root") {
+            options.compareRoot = path.resolve(args[++index]);
+        } else {
+            throw new Error(`Unknown argument: ${argument}`);
+        }
+    }
+
+    return options;
+}
+
+function collectModules(options) {
+    if (options.module) {
+        return [options.module];
+    }
+
+    return fs.readdirSync(options.root, { withFileTypes: true })
+        .filter(entry => entry.isDirectory())
+        .map(entry => entry.name)
+        .filter(module => options.targets.some(target =>
+            fs.existsSync(graphPath(options.root, module, target))))
+        .sort();
+}
+
+function graphPath(root, module, target) {
+    return path.join(root, module, "target", "codegraph", target, 
`${module}.json`);
+}
+
+function findUnresolved(value, location, unresolved) {
+    if (!value || typeof value !== "object") {
+        return;
+    }
+    if (value.unresolved === true) {
+        unresolved.push(location);
+    }
+    if (Array.isArray(value)) {
+        value.forEach((item, index) => findUnresolved(item, 
`${location}[${index}]`, unresolved));
+        return;
+    }
+    Object.keys(value).forEach(key => findUnresolved(value[key], 
`${location}.${key}`, unresolved));
+}
+
+function validateDeclarations(graph, file) {
+    if (!Array.isArray(graph.symbols)) {
+        throw new Error(`${file}: symbols must be an array`);
+    }
+
+    const ids = new Set();
+    graph.symbols.forEach((symbol, symbolIndex) => {
+        const declarations = [symbol].concat(Array.isArray(symbol.members) ? 
symbol.members : []);
+        declarations.forEach((declaration, declarationIndex) => {
+            if (typeof declaration.id !== "string" || declaration.id.length 
=== 0) {
+                throw new Error(`${file}: declaration 
${symbolIndex}:${declarationIndex} has no id`);
+            }
+            if (ids.has(declaration.id)) {
+                throw new Error(`${file}: duplicate declaration id 
${declaration.id}`);
+            }
+            ids.add(declaration.id);
+        });
+    });
+}
+
+function validateGraph(options, module, target) {
+    const file = graphPath(options.root, module, target);
+    if (!fs.existsSync(file)) {
+        throw new Error(`${file}: expected graph does not exist`);
+    }
+
+    const contents = fs.readFileSync(file);
+    const graph = JSON.parse(contents.toString("utf8"));
+    if (graph.schemaVersion !== "1.0") {
+        throw new Error(`${file}: expected schemaVersion 1.0, found 
${graph.schemaVersion}`);
+    }
+    if (graph.module !== module) {
+        throw new Error(`${file}: expected module ${module}, found 
${graph.module}`);
+    }
+    if (graph.target !== target) {
+        throw new Error(`${file}: expected target ${target}, found 
${graph.target}`);
+    }
+
+    validateDeclarations(graph, file);
+    const unresolved = [];
+    findUnresolved(graph, "$", unresolved);
+    const unresolvedApi = unresolved.filter(location => 
location.indexOf(".metadata[") === -1);
+    if (unresolvedApi.length > 0) {
+        throw new Error(`${file}: ${unresolvedApi.length} unresolved API 
reference(s), first at ${unresolvedApi[0]}`);
+    }
+
+    if (options.compareRoot) {
+        const comparisonFile = graphPath(options.compareRoot, module, target);
+        if (!fs.existsSync(comparisonFile) || 
!contents.equals(fs.readFileSync(comparisonFile))) {
+            throw new Error(`${file}: differs from ${comparisonFile}`);
+        }
+    }
+    return unresolved.length - unresolvedApi.length;
+}
+
+function main() {
+    const options = parseArguments(process.argv.slice(2));
+    const modules = collectModules(options);
+    if (modules.length === 0) {
+        throw new Error(`No codegraph modules found under ${options.root}`);
+    }
+
+    let unresolvedMetadata = 0;
+    modules.forEach(module => options.targets.forEach(target => {
+        unresolvedMetadata += validateGraph(options, module, target);
+    }));
+    console.log(`Validated ${modules.length * options.targets.length} 
codegraph shard(s) for ${modules.length} module(s).`);
+    if (unresolvedMetadata > 0) {
+        console.log(`Preserved ${unresolvedMetadata} unresolved metadata 
reference name(s).`);
+    }
+}
+
+try {
+    main();
+} catch (error) {
+    console.error(error.message);
+    process.exitCode = 1;
+}
\ No newline at end of file

Reply via email to