vue-hook-optimizer 0.0.81 → 0.0.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +205 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +205 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1691,34 +1691,210 @@ function getMermaidText(graph, nodesUsedInTemplate, nodesUsedInStyle = /* @__PUR
|
|
|
1691
1691
|
|
|
1692
1692
|
//#endregion
|
|
1693
1693
|
//#region src/suggest/community.ts
|
|
1694
|
-
|
|
1695
|
-
|
|
1694
|
+
const COMMON_PREFIXES = [
|
|
1695
|
+
"handle",
|
|
1696
|
+
"on",
|
|
1697
|
+
"is",
|
|
1698
|
+
"has",
|
|
1699
|
+
"can",
|
|
1700
|
+
"should",
|
|
1701
|
+
"get",
|
|
1702
|
+
"set",
|
|
1703
|
+
"update",
|
|
1704
|
+
"toggle",
|
|
1705
|
+
"reset",
|
|
1706
|
+
"clear",
|
|
1707
|
+
"init",
|
|
1708
|
+
"fetch",
|
|
1709
|
+
"load",
|
|
1710
|
+
"save",
|
|
1711
|
+
"delete",
|
|
1712
|
+
"remove",
|
|
1713
|
+
"add",
|
|
1714
|
+
"create",
|
|
1715
|
+
"show",
|
|
1716
|
+
"hide",
|
|
1717
|
+
"open",
|
|
1718
|
+
"close",
|
|
1719
|
+
"enable",
|
|
1720
|
+
"disable",
|
|
1721
|
+
"validate",
|
|
1722
|
+
"check",
|
|
1723
|
+
"use"
|
|
1724
|
+
];
|
|
1725
|
+
const COMMON_SUFFIXES = [
|
|
1726
|
+
"change",
|
|
1727
|
+
"changed",
|
|
1728
|
+
"handler",
|
|
1729
|
+
"callback",
|
|
1730
|
+
"listener",
|
|
1731
|
+
"state",
|
|
1732
|
+
"value",
|
|
1733
|
+
"data",
|
|
1734
|
+
"list",
|
|
1735
|
+
"items",
|
|
1736
|
+
"count",
|
|
1737
|
+
"index",
|
|
1738
|
+
"id",
|
|
1739
|
+
"name",
|
|
1740
|
+
"type",
|
|
1741
|
+
"status",
|
|
1742
|
+
"error",
|
|
1743
|
+
"loading",
|
|
1744
|
+
"visible",
|
|
1745
|
+
"disabled",
|
|
1746
|
+
"enabled",
|
|
1747
|
+
"active",
|
|
1748
|
+
"selected",
|
|
1749
|
+
"checked",
|
|
1750
|
+
"open",
|
|
1751
|
+
"closed"
|
|
1752
|
+
];
|
|
1753
|
+
/**
|
|
1754
|
+
* Extract the base/root word from an identifier by removing common prefixes/suffixes.
|
|
1755
|
+
* Only removes prefixes at the start and suffixes at the end.
|
|
1756
|
+
* e.g., "handleOpenChange" -> ["open"]
|
|
1757
|
+
* "isVisible" -> ["visible"]
|
|
1758
|
+
* "userName" -> ["user", "name"]
|
|
1759
|
+
*/
|
|
1760
|
+
function extractBaseWords(identifier) {
|
|
1761
|
+
const tokens = identifier.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase().split(/[_\-\s]+/).filter(Boolean);
|
|
1762
|
+
if (tokens.length === 0) return [];
|
|
1763
|
+
if (tokens.length === 1) return tokens;
|
|
1764
|
+
let start = 0;
|
|
1765
|
+
if (COMMON_PREFIXES.includes(tokens[0])) start = 1;
|
|
1766
|
+
let end = tokens.length;
|
|
1767
|
+
if (COMMON_SUFFIXES.includes(tokens[tokens.length - 1])) end = tokens.length - 1;
|
|
1768
|
+
const words = tokens.slice(start, end);
|
|
1769
|
+
if (words.length === 0) return tokens.slice(start > 0 ? start : 0);
|
|
1770
|
+
return words;
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Calculate semantic similarity using cached base words.
|
|
1774
|
+
* Returns a value between 0 and 1.
|
|
1775
|
+
*/
|
|
1776
|
+
function calculateSemanticSimilarityCached(labelA, labelB, wordsA, wordsB) {
|
|
1777
|
+
if (labelA === labelB) return 1;
|
|
1778
|
+
const lowerA = labelA.toLowerCase();
|
|
1779
|
+
const lowerB = labelB.toLowerCase();
|
|
1780
|
+
if (lowerA.includes(lowerB) || lowerB.includes(lowerA)) {
|
|
1781
|
+
const shorter = lowerA.length < lowerB.length ? lowerA : lowerB;
|
|
1782
|
+
const longer = lowerA.length < lowerB.length ? lowerB : lowerA;
|
|
1783
|
+
return shorter.length / longer.length;
|
|
1784
|
+
}
|
|
1785
|
+
if (wordsA.length === 0 || wordsB.length === 0) return 0;
|
|
1786
|
+
const setA = new Set(wordsA);
|
|
1787
|
+
const setB = new Set(wordsB);
|
|
1788
|
+
let sharedCount = 0;
|
|
1789
|
+
for (const word of setA) if (setB.has(word)) sharedCount++;
|
|
1790
|
+
if (sharedCount === 0) return 0;
|
|
1791
|
+
return sharedCount / (setA.size + setB.size - sharedCount);
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Build a weighted graph that combines structural connections with semantic similarity.
|
|
1795
|
+
*
|
|
1796
|
+
* Optimized algorithm:
|
|
1797
|
+
* 1. Cache extractBaseWords results to avoid repeated computation
|
|
1798
|
+
* 2. Build word-to-nodes bucket map, only compare nodes within same bucket
|
|
1799
|
+
* This reduces O(N²) to O(B × K²) where B = number of buckets, K = avg nodes per bucket
|
|
1800
|
+
*/
|
|
1801
|
+
function buildWeightedGraph(graph, options = {}) {
|
|
1802
|
+
const { semanticWeight = 1, similarityThreshold = .3 } = options;
|
|
1803
|
+
const weighted = /* @__PURE__ */ new Map();
|
|
1804
|
+
const allNodes = /* @__PURE__ */ new Set();
|
|
1805
|
+
const connectedPairs = /* @__PURE__ */ new Set();
|
|
1696
1806
|
for (const [node, edges] of graph) {
|
|
1697
|
-
|
|
1698
|
-
for (const edge of edges)
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1807
|
+
allNodes.add(node);
|
|
1808
|
+
for (const edge of edges) allNodes.add(edge.node);
|
|
1809
|
+
}
|
|
1810
|
+
for (const node of allNodes) weighted.set(node, /* @__PURE__ */ new Map());
|
|
1811
|
+
const structuralWeight = .85;
|
|
1812
|
+
for (const [node, edges] of graph) for (const edge of edges) {
|
|
1813
|
+
const currentWeight = weighted.get(node).get(edge.node) || 0;
|
|
1814
|
+
weighted.get(node).set(edge.node, Math.max(currentWeight, structuralWeight));
|
|
1815
|
+
const reverseWeight = weighted.get(edge.node).get(node) || 0;
|
|
1816
|
+
weighted.get(edge.node).set(node, Math.max(reverseWeight, structuralWeight));
|
|
1817
|
+
const pairKey = [node.label, edge.node.label].sort().join("|");
|
|
1818
|
+
connectedPairs.add(pairKey);
|
|
1819
|
+
}
|
|
1820
|
+
if (semanticWeight > 0) {
|
|
1821
|
+
const nodeWordsCache = /* @__PURE__ */ new Map();
|
|
1822
|
+
const wordToBucket = /* @__PURE__ */ new Map();
|
|
1823
|
+
for (const node of allNodes) {
|
|
1824
|
+
const words = extractBaseWords(node.label);
|
|
1825
|
+
nodeWordsCache.set(node, words);
|
|
1826
|
+
for (const word of words) {
|
|
1827
|
+
if (!wordToBucket.has(word)) wordToBucket.set(word, /* @__PURE__ */ new Set());
|
|
1828
|
+
wordToBucket.get(word).add(node);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
const comparedPairs = /* @__PURE__ */ new Set();
|
|
1832
|
+
for (const [_, bucket] of wordToBucket) {
|
|
1833
|
+
if (bucket.size < 2) continue;
|
|
1834
|
+
const bucketNodes = Array.from(bucket);
|
|
1835
|
+
for (let i = 0; i < bucketNodes.length; i++) for (let j = i + 1; j < bucketNodes.length; j++) {
|
|
1836
|
+
const nodeA = bucketNodes[i];
|
|
1837
|
+
const nodeB = bucketNodes[j];
|
|
1838
|
+
const comparedKey = [nodeA.label, nodeB.label].sort().join("|");
|
|
1839
|
+
if (comparedPairs.has(comparedKey)) continue;
|
|
1840
|
+
comparedPairs.add(comparedKey);
|
|
1841
|
+
const wordsA = nodeWordsCache.get(nodeA);
|
|
1842
|
+
const wordsB = nodeWordsCache.get(nodeB);
|
|
1843
|
+
const similarity = calculateSemanticSimilarityCached(nodeA.label, nodeB.label, wordsA, wordsB);
|
|
1844
|
+
if (similarity > similarityThreshold) {
|
|
1845
|
+
const isConnected = connectedPairs.has(comparedKey);
|
|
1846
|
+
const semanticEdgeWeight = similarity * semanticWeight;
|
|
1847
|
+
const currentAB = weighted.get(nodeA).get(nodeB) || 0;
|
|
1848
|
+
const newWeightAB = isConnected ? Math.max(currentAB, semanticEdgeWeight) : currentAB + semanticEdgeWeight;
|
|
1849
|
+
weighted.get(nodeA).set(nodeB, Math.min(newWeightAB, 2));
|
|
1850
|
+
const currentBA = weighted.get(nodeB).get(nodeA) || 0;
|
|
1851
|
+
const newWeightBA = isConnected ? Math.max(currentBA, semanticEdgeWeight) : currentBA + semanticEdgeWeight;
|
|
1852
|
+
weighted.get(nodeB).set(nodeA, Math.min(newWeightBA, 2));
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1702
1855
|
}
|
|
1703
1856
|
}
|
|
1704
|
-
return
|
|
1857
|
+
return weighted;
|
|
1705
1858
|
}
|
|
1706
|
-
function
|
|
1707
|
-
|
|
1859
|
+
function createSeededRandom(seed) {
|
|
1860
|
+
if (seed === void 0) return Math.random;
|
|
1861
|
+
let state = seed;
|
|
1862
|
+
return () => {
|
|
1863
|
+
state = state * 1103515245 + 12345 & 2147483647;
|
|
1864
|
+
return state / 2147483647;
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
function shuffleArray(array, random = Math.random) {
|
|
1868
|
+
const result = [...array];
|
|
1869
|
+
for (let i = result.length - 1; i > 0; i--) {
|
|
1870
|
+
const j = Math.floor(random() * (i + 1));
|
|
1871
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
1872
|
+
}
|
|
1873
|
+
return result;
|
|
1708
1874
|
}
|
|
1709
1875
|
/**
|
|
1710
|
-
* Label Propagation Algorithm for community detection.
|
|
1876
|
+
* Label Propagation Algorithm for community detection with semantic awareness.
|
|
1711
1877
|
*
|
|
1712
1878
|
* Each node starts with its own unique label. In each iteration,
|
|
1713
|
-
* nodes adopt the most frequent label among their neighbors
|
|
1714
|
-
*
|
|
1879
|
+
* nodes adopt the most frequent label among their neighbors,
|
|
1880
|
+
* weighted by both structural connections and semantic similarity.
|
|
1881
|
+
*
|
|
1882
|
+
* Semantic similarity considers:
|
|
1883
|
+
* - Shared base words (e.g., "open" in "isOpen" and "handleOpenChange")
|
|
1884
|
+
* - Substring relationships
|
|
1885
|
+
* - Common naming patterns (handler/state pairs)
|
|
1715
1886
|
*
|
|
1716
1887
|
* This helps identify groups of nodes that are tightly connected
|
|
1717
1888
|
* and could potentially be extracted into separate hooks.
|
|
1718
1889
|
*/
|
|
1719
|
-
function detectCommunities(graph,
|
|
1720
|
-
const
|
|
1721
|
-
const
|
|
1890
|
+
function detectCommunities(graph, options = {}) {
|
|
1891
|
+
const { maxIterations = 100, semanticWeight = 1, similarityThreshold = .3 } = options;
|
|
1892
|
+
const random = createSeededRandom(options.seed);
|
|
1893
|
+
const weightedGraph = buildWeightedGraph(graph, {
|
|
1894
|
+
semanticWeight,
|
|
1895
|
+
similarityThreshold
|
|
1896
|
+
});
|
|
1897
|
+
const nodes = Array.from(weightedGraph.keys());
|
|
1722
1898
|
if (nodes.length === 0) return {
|
|
1723
1899
|
communities: [],
|
|
1724
1900
|
nodeToCommuntiy: /* @__PURE__ */ new Map()
|
|
@@ -1732,24 +1908,24 @@ function detectCommunities(graph, maxIterations = 100) {
|
|
|
1732
1908
|
while (changed && iterations < maxIterations) {
|
|
1733
1909
|
changed = false;
|
|
1734
1910
|
iterations++;
|
|
1735
|
-
const
|
|
1736
|
-
for (const node of
|
|
1737
|
-
const neighbors =
|
|
1911
|
+
const shuffledNodes = shuffleArray(nodes, random);
|
|
1912
|
+
for (const node of shuffledNodes) {
|
|
1913
|
+
const neighbors = weightedGraph.get(node);
|
|
1738
1914
|
if (!neighbors || neighbors.size === 0) continue;
|
|
1739
|
-
const
|
|
1740
|
-
for (const neighbor of neighbors) {
|
|
1915
|
+
const labelWeights = /* @__PURE__ */ new Map();
|
|
1916
|
+
for (const [neighbor, weight] of neighbors) {
|
|
1741
1917
|
const neighborLabel = labels.get(neighbor);
|
|
1742
|
-
|
|
1918
|
+
labelWeights.set(neighborLabel, (labelWeights.get(neighborLabel) || 0) + weight);
|
|
1743
1919
|
}
|
|
1744
|
-
let
|
|
1920
|
+
let maxWeight = 0;
|
|
1745
1921
|
let maxLabels = [];
|
|
1746
|
-
for (const [label,
|
|
1747
|
-
|
|
1922
|
+
for (const [label, weight] of labelWeights) if (weight > maxWeight) {
|
|
1923
|
+
maxWeight = weight;
|
|
1748
1924
|
maxLabels = [label];
|
|
1749
|
-
} else if (
|
|
1925
|
+
} else if (weight === maxWeight) maxLabels.push(label);
|
|
1750
1926
|
const currentLabel = labels.get(node);
|
|
1751
1927
|
if (maxLabels.includes(currentLabel)) continue;
|
|
1752
|
-
const newLabel = Math.
|
|
1928
|
+
const newLabel = maxLabels[Math.floor(random() * maxLabels.length)];
|
|
1753
1929
|
if (newLabel !== currentLabel) {
|
|
1754
1930
|
labels.set(node, newLabel);
|
|
1755
1931
|
changed = true;
|
|
@@ -2052,7 +2228,7 @@ let SuggestionType = /* @__PURE__ */ function(SuggestionType$1) {
|
|
|
2052
2228
|
return SuggestionType$1;
|
|
2053
2229
|
}({});
|
|
2054
2230
|
function gen(graph, nodesUsedInTemplate, nodesUsedInStyle = /* @__PURE__ */ new Set(), options) {
|
|
2055
|
-
const { ellipsis = true } = options ?? {};
|
|
2231
|
+
const { ellipsis = true, communitySeed } = options ?? {};
|
|
2056
2232
|
const usedNodes = new Set([...nodesUsedInTemplate, ...nodesUsedInStyle]);
|
|
2057
2233
|
const suggestions = [];
|
|
2058
2234
|
const splitedGraph = splitGraph(graph.edges);
|
|
@@ -2106,7 +2282,7 @@ function gen(graph, nodesUsedInTemplate, nodesUsedInStyle = /* @__PURE__ */ new
|
|
|
2106
2282
|
nodeInfo: node
|
|
2107
2283
|
});
|
|
2108
2284
|
});
|
|
2109
|
-
const communityResult = detectCommunities(graph.edges);
|
|
2285
|
+
const communityResult = detectCommunities(graph.edges, { seed: communitySeed });
|
|
2110
2286
|
const { communities } = communityResult;
|
|
2111
2287
|
const extractableCommunities = communities.filter((community) => {
|
|
2112
2288
|
const nodes = Array.from(community.nodes);
|