@lzehrung/codegraph 3.0.0 → 3.1.0
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/README.md +19 -17
- package/codegraph-skill/codegraph/SKILL.md +3 -0
- package/dist/agent/artifact.d.ts.map +1 -1
- package/dist/agent/artifact.js +4 -9
- package/dist/agent/artifact.js.map +1 -1
- package/dist/agent/call-hierarchy.d.ts +4 -2
- package/dist/agent/call-hierarchy.d.ts.map +1 -1
- package/dist/agent/call-hierarchy.js +20 -29
- package/dist/agent/call-hierarchy.js.map +1 -1
- package/dist/agent/explain.d.ts.map +1 -1
- package/dist/agent/explain.js +12 -11
- package/dist/agent/explain.js.map +1 -1
- package/dist/agent/explore.d.ts.map +1 -1
- package/dist/agent/explore.js +2 -11
- package/dist/agent/explore.js.map +1 -1
- package/dist/agent/file-view.d.ts.map +1 -1
- package/dist/agent/file-view.js +4 -12
- package/dist/agent/file-view.js.map +1 -1
- package/dist/agent/normalize.d.ts +0 -3
- package/dist/agent/normalize.d.ts.map +1 -1
- package/dist/agent/normalize.js +1 -7
- package/dist/agent/normalize.js.map +1 -1
- package/dist/agent/orient.d.ts.map +1 -1
- package/dist/agent/orient.js +2 -6
- package/dist/agent/orient.js.map +1 -1
- package/dist/agent/packet.d.ts.map +1 -1
- package/dist/agent/packet.js +2 -5
- package/dist/agent/packet.js.map +1 -1
- package/dist/agent/query-index/content.d.ts.map +1 -1
- package/dist/agent/query-index/content.js +2 -6
- package/dist/agent/query-index/content.js.map +1 -1
- package/dist/agent/query-index/update.d.ts.map +1 -1
- package/dist/agent/query-index/update.js +8 -45
- package/dist/agent/query-index/update.js.map +1 -1
- package/dist/agent/query-index/worker-pool.d.ts.map +1 -1
- package/dist/agent/query-index/worker-pool.js +2 -1
- package/dist/agent/query-index/worker-pool.js.map +1 -1
- package/dist/agent/refactor-plan.d.ts +2 -2
- package/dist/agent/refactor-plan.d.ts.map +1 -1
- package/dist/agent/refactor-plan.js +11 -57
- package/dist/agent/refactor-plan.js.map +1 -1
- package/dist/agent/rename-preview.d.ts.map +1 -1
- package/dist/agent/rename-preview.js +218 -26
- package/dist/agent/rename-preview.js.map +1 -1
- package/dist/agent/search.d.ts.map +1 -1
- package/dist/agent/search.js +7 -22
- package/dist/agent/search.js.map +1 -1
- package/dist/agent/semantic-symbols.d.ts +1 -1
- package/dist/agent/semantic-symbols.d.ts.map +1 -1
- package/dist/agent/semantic-symbols.js +2 -5
- package/dist/agent/semantic-symbols.js.map +1 -1
- package/dist/agent/semantic.d.ts +5 -0
- package/dist/agent/semantic.d.ts.map +1 -1
- package/dist/agent/semantic.js +12 -1
- package/dist/agent/semantic.js.map +1 -1
- package/dist/agent/session.d.ts +9 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +59 -41
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/type-hierarchy.d.ts +4 -2
- package/dist/agent/type-hierarchy.d.ts.map +1 -1
- package/dist/agent/type-hierarchy.js +19 -31
- package/dist/agent/type-hierarchy.js.map +1 -1
- package/dist/agent/workspace-symbols.d.ts +1 -1
- package/dist/agent/workspace-symbols.d.ts.map +1 -1
- package/dist/agent/workspace-symbols.js +7 -15
- package/dist/agent/workspace-symbols.js.map +1 -1
- package/dist/analysis-summary.d.ts +1 -4
- package/dist/analysis-summary.d.ts.map +1 -1
- package/dist/analysis-summary.js +58 -19
- package/dist/analysis-summary.js.map +1 -1
- package/dist/bin/cli.js +36572 -22925
- package/dist/bin/query-index-worker.js +440 -261
- package/dist/bootstrap/tree-sitter-languages.d.ts +2 -0
- package/dist/bootstrap/tree-sitter-languages.d.ts.map +1 -1
- package/dist/bootstrap/tree-sitter-languages.js +6 -19
- package/dist/bootstrap/tree-sitter-languages.js.map +1 -1
- package/dist/chunking/chunk-matches.d.ts.map +1 -1
- package/dist/chunking/chunk-matches.js +6 -42
- package/dist/chunking/chunk-matches.js.map +1 -1
- package/dist/chunking/chunk-split.d.ts +7 -0
- package/dist/chunking/chunk-split.d.ts.map +1 -1
- package/dist/chunking/chunk-split.js +3 -3
- package/dist/chunking/chunk-split.js.map +1 -1
- package/dist/chunking/chunk-text-file.js +2 -30
- package/dist/chunking/chunk-text-file.js.map +1 -1
- package/dist/cli/affected.d.ts.map +1 -1
- package/dist/cli/affected.js +1 -9
- package/dist/cli/affected.js.map +1 -1
- package/dist/cli/call-hierarchy.js +3 -6
- package/dist/cli/call-hierarchy.js.map +1 -1
- package/dist/cli/chunk.d.ts.map +1 -1
- package/dist/cli/chunk.js +4 -11
- package/dist/cli/chunk.js.map +1 -1
- package/dist/cli/command-table.d.ts.map +1 -1
- package/dist/cli/command-table.js +3 -0
- package/dist/cli/command-table.js.map +1 -1
- package/dist/cli/context.d.ts +1 -0
- package/dist/cli/context.d.ts.map +1 -1
- package/dist/cli/context.js +1 -1
- package/dist/cli/context.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +1 -11
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/graph.d.ts +4 -12
- package/dist/cli/graph.d.ts.map +1 -1
- package/dist/cli/graph.js +17 -8
- package/dist/cli/graph.js.map +1 -1
- package/dist/cli/help.d.ts +4 -4
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +7 -5
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/index.d.ts +2 -12
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/links.d.ts.map +1 -1
- package/dist/cli/links.js +4 -10
- package/dist/cli/links.js.map +1 -1
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +1 -0
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/module-locals.d.ts +8 -0
- package/dist/cli/module-locals.d.ts.map +1 -0
- package/dist/cli/module-locals.js +9 -0
- package/dist/cli/module-locals.js.map +1 -0
- package/dist/cli/navigation.d.ts +2 -0
- package/dist/cli/navigation.d.ts.map +1 -1
- package/dist/cli/navigation.js +3 -5
- package/dist/cli/navigation.js.map +1 -1
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/cli/options.js +2 -1
- package/dist/cli/options.js.map +1 -1
- package/dist/cli/pretty.d.ts +1 -0
- package/dist/cli/pretty.d.ts.map +1 -1
- package/dist/cli/pretty.js +6 -8
- package/dist/cli/pretty.js.map +1 -1
- package/dist/cli/refactor-plan.js +2 -5
- package/dist/cli/refactor-plan.js.map +1 -1
- package/dist/cli/rename-preview.js +2 -5
- package/dist/cli/rename-preview.js.map +1 -1
- package/dist/cli/review.d.ts +6 -16
- package/dist/cli/review.d.ts.map +1 -1
- package/dist/cli/review.js +2 -0
- package/dist/cli/review.js.map +1 -1
- package/dist/cli/server.d.ts.map +1 -1
- package/dist/cli/server.js +7 -10
- package/dist/cli/server.js.map +1 -1
- package/dist/cli/type-hierarchy.js +8 -8
- package/dist/cli/type-hierarchy.js.map +1 -1
- package/dist/document-links/asciidoc.d.ts.map +1 -1
- package/dist/document-links/asciidoc.js +4 -16
- package/dist/document-links/asciidoc.js.map +1 -1
- package/dist/document-links/check.d.ts.map +1 -1
- package/dist/document-links/check.js +1 -3
- package/dist/document-links/check.js.map +1 -1
- package/dist/document-links/html.d.ts.map +1 -1
- package/dist/document-links/html.js +6 -44
- package/dist/document-links/html.js.map +1 -1
- package/dist/document-links/markdown.d.ts.map +1 -1
- package/dist/document-links/markdown.js +4 -13
- package/dist/document-links/markdown.js.map +1 -1
- package/dist/document-links/rst.d.ts.map +1 -1
- package/dist/document-links/rst.js +7 -31
- package/dist/document-links/rst.js.map +1 -1
- package/dist/document-links/sfc.d.ts.map +1 -1
- package/dist/document-links/sfc.js +2 -5
- package/dist/document-links/sfc.js.map +1 -1
- package/dist/document-links/shared.d.ts +2 -0
- package/dist/document-links/shared.d.ts.map +1 -1
- package/dist/document-links/shared.js +13 -0
- package/dist/document-links/shared.js.map +1 -1
- package/dist/drift/artifact.d.ts.map +1 -1
- package/dist/drift/artifact.js +4 -27
- package/dist/drift/artifact.js.map +1 -1
- package/dist/drift/graph-edges.d.ts +4 -0
- package/dist/drift/graph-edges.d.ts.map +1 -0
- package/dist/drift/graph-edges.js +15 -0
- package/dist/drift/graph-edges.js.map +1 -0
- package/dist/drift/snapshot.d.ts.map +1 -1
- package/dist/drift/snapshot.js +2 -17
- package/dist/drift/snapshot.js.map +1 -1
- package/dist/duplicates/unit-cache.d.ts +11 -1
- package/dist/duplicates/unit-cache.d.ts.map +1 -1
- package/dist/duplicates/unit-cache.js +20 -2
- package/dist/duplicates/unit-cache.js.map +1 -1
- package/dist/duplicates/units.d.ts +2 -1
- package/dist/duplicates/units.d.ts.map +1 -1
- package/dist/duplicates/units.js +23 -22
- package/dist/duplicates/units.js.map +1 -1
- package/dist/graph-builder.d.ts +1 -1
- package/dist/graph-builder.d.ts.map +1 -1
- package/dist/graph-builder.js +3 -2
- package/dist/graph-builder.js.map +1 -1
- package/dist/graph-edge-collector.d.ts +3 -1
- package/dist/graph-edge-collector.d.ts.map +1 -1
- package/dist/graph-edge-collector.js +38 -6
- package/dist/graph-edge-collector.js.map +1 -1
- package/dist/graphs/angularjs.d.ts.map +1 -1
- package/dist/graphs/angularjs.js +2 -9
- package/dist/graphs/angularjs.js.map +1 -1
- package/dist/graphs/edge-resolution.d.ts +4 -0
- package/dist/graphs/edge-resolution.d.ts.map +1 -1
- package/dist/graphs/edge-resolution.js +2 -0
- package/dist/graphs/edge-resolution.js.map +1 -1
- package/dist/graphs/external/manifests.d.ts.map +1 -1
- package/dist/graphs/external/manifests.js +1 -6
- package/dist/graphs/external/manifests.js.map +1 -1
- package/dist/graphs/specifiers.d.ts.map +1 -1
- package/dist/graphs/specifiers.js +47 -26
- package/dist/graphs/specifiers.js.map +1 -1
- package/dist/graphs/symbol-graph-detailed/edge-passes.d.ts +2 -0
- package/dist/graphs/symbol-graph-detailed/edge-passes.d.ts.map +1 -1
- package/dist/graphs/symbol-graph-detailed/edge-passes.js +9 -3
- package/dist/graphs/symbol-graph-detailed/edge-passes.js.map +1 -1
- package/dist/graphs/symbol-graph-detailed/receiver-calls.d.ts +3 -3
- package/dist/graphs/symbol-graph-detailed/receiver-calls.d.ts.map +1 -1
- package/dist/graphs/symbol-graph-detailed/receiver-calls.js +2 -26
- package/dist/graphs/symbol-graph-detailed/receiver-calls.js.map +1 -1
- package/dist/graphs/symbol-graph-detailed.d.ts.map +1 -1
- package/dist/graphs/symbol-graph-detailed.js +38 -1
- package/dist/graphs/symbol-graph-detailed.js.map +1 -1
- package/dist/graphs/symbol-graph.d.ts +1 -1
- package/dist/graphs/symbol-graph.d.ts.map +1 -1
- package/dist/graphs/symbol-graph.js +8 -0
- package/dist/graphs/symbol-graph.js.map +1 -1
- package/dist/impact/analyzer.d.ts.map +1 -1
- package/dist/impact/analyzer.js +116 -17
- package/dist/impact/analyzer.js.map +1 -1
- package/dist/impact/budgets.d.ts.map +1 -1
- package/dist/impact/budgets.js +3 -9
- package/dist/impact/budgets.js.map +1 -1
- package/dist/impact/call-compatibility.d.ts.map +1 -1
- package/dist/impact/call-compatibility.js +3 -12
- package/dist/impact/call-compatibility.js.map +1 -1
- package/dist/impact/context.d.ts +6 -0
- package/dist/impact/context.d.ts.map +1 -1
- package/dist/impact/context.js +2 -2
- package/dist/impact/context.js.map +1 -1
- package/dist/impact/direct.d.ts.map +1 -1
- package/dist/impact/direct.js +2 -6
- package/dist/impact/direct.js.map +1 -1
- package/dist/impact/map.d.ts +3 -1
- package/dist/impact/map.d.ts.map +1 -1
- package/dist/impact/map.js +12 -5
- package/dist/impact/map.js.map +1 -1
- package/dist/impact/reference-cache.d.ts.map +1 -1
- package/dist/impact/reference-cache.js +1 -24
- package/dist/impact/reference-cache.js.map +1 -1
- package/dist/impact/report-shared.d.ts +2 -1
- package/dist/impact/report-shared.d.ts.map +1 -1
- package/dist/impact/report-shared.js +10 -0
- package/dist/impact/report-shared.js.map +1 -1
- package/dist/impact/report-suggestions.d.ts.map +1 -1
- package/dist/impact/report-suggestions.js +2 -8
- package/dist/impact/report-suggestions.js.map +1 -1
- package/dist/impact/report.d.ts.map +1 -1
- package/dist/impact/report.js +2 -8
- package/dist/impact/report.js.map +1 -1
- package/dist/impact/streaming.d.ts.map +1 -1
- package/dist/impact/streaming.js +3 -8
- package/dist/impact/streaming.js.map +1 -1
- package/dist/impact/transitive.d.ts +6 -2
- package/dist/impact/transitive.d.ts.map +1 -1
- package/dist/impact/transitive.js +23 -11
- package/dist/impact/transitive.js.map +1 -1
- package/dist/indexer/build-cache/manifest.d.ts.map +1 -1
- package/dist/indexer/build-cache/manifest.js +10 -108
- package/dist/indexer/build-cache/manifest.js.map +1 -1
- package/dist/indexer/build-cache/module-cache.d.ts +6 -1
- package/dist/indexer/build-cache/module-cache.d.ts.map +1 -1
- package/dist/indexer/build-cache/module-cache.js +25 -2
- package/dist/indexer/build-cache/module-cache.js.map +1 -1
- package/dist/indexer/build-cache/options.d.ts +40 -1
- package/dist/indexer/build-cache/options.d.ts.map +1 -1
- package/dist/indexer/build-cache/options.js +40 -1
- package/dist/indexer/build-cache/options.js.map +1 -1
- package/dist/indexer/build-cache/project-snapshot.d.ts.map +1 -1
- package/dist/indexer/build-cache/project-snapshot.js +52 -99
- package/dist/indexer/build-cache/project-snapshot.js.map +1 -1
- package/dist/indexer/build-cache/reports.d.ts +1 -0
- package/dist/indexer/build-cache/reports.d.ts.map +1 -1
- package/dist/indexer/build-cache/reports.js +10 -0
- package/dist/indexer/build-cache/reports.js.map +1 -1
- package/dist/indexer/build-index.d.ts +2 -0
- package/dist/indexer/build-index.d.ts.map +1 -1
- package/dist/indexer/build-index.js +187 -57
- package/dist/indexer/build-index.js.map +1 -1
- package/dist/indexer/build-workers.d.ts +7 -1
- package/dist/indexer/build-workers.d.ts.map +1 -1
- package/dist/indexer/build-workers.js +12 -0
- package/dist/indexer/build-workers.js.map +1 -1
- package/dist/indexer/call-hierarchy.d.ts.map +1 -1
- package/dist/indexer/call-hierarchy.js +5 -17
- package/dist/indexer/call-hierarchy.js.map +1 -1
- package/dist/indexer/compilation-units.d.ts +32 -20
- package/dist/indexer/compilation-units.d.ts.map +1 -1
- package/dist/indexer/compilation-units.js +139 -145
- package/dist/indexer/compilation-units.js.map +1 -1
- package/dist/indexer/cpp-callables.d.ts.map +1 -1
- package/dist/indexer/cpp-callables.js +2 -7
- package/dist/indexer/cpp-callables.js.map +1 -1
- package/dist/indexer/declarations.d.ts.map +1 -1
- package/dist/indexer/declarations.js +32 -7
- package/dist/indexer/declarations.js.map +1 -1
- package/dist/indexer/import-resolution-tables.d.ts.map +1 -1
- package/dist/indexer/import-resolution-tables.js +10 -0
- package/dist/indexer/import-resolution-tables.js.map +1 -1
- package/dist/indexer/import-types.d.ts +5 -0
- package/dist/indexer/import-types.d.ts.map +1 -1
- package/dist/indexer/import-types.js.map +1 -1
- package/dist/indexer/imports/binding-ranges.d.ts +5 -12
- package/dist/indexer/imports/binding-ranges.d.ts.map +1 -1
- package/dist/indexer/imports/binding-ranges.js +13 -19
- package/dist/indexer/imports/binding-ranges.js.map +1 -1
- package/dist/indexer/imports/context.d.ts +6 -0
- package/dist/indexer/imports/context.d.ts.map +1 -1
- package/dist/indexer/imports/import-binding-tables.d.ts +28 -13
- package/dist/indexer/imports/import-binding-tables.d.ts.map +1 -1
- package/dist/indexer/imports/import-binding-tables.js +182 -178
- package/dist/indexer/imports/import-binding-tables.js.map +1 -1
- package/dist/indexer/imports/js-text-imports.d.ts +0 -1
- package/dist/indexer/imports/js-text-imports.d.ts.map +1 -1
- package/dist/indexer/imports/js-text-imports.js +0 -176
- package/dist/indexer/imports/js-text-imports.js.map +1 -1
- package/dist/indexer/imports/language-specific.d.ts.map +1 -1
- package/dist/indexer/imports/language-specific.js +4 -8
- package/dist/indexer/imports/language-specific.js.map +1 -1
- package/dist/indexer/imports/python.d.ts +0 -1
- package/dist/indexer/imports/python.d.ts.map +1 -1
- package/dist/indexer/imports/python.js +1 -29
- package/dist/indexer/imports/python.js.map +1 -1
- package/dist/indexer/imports/text-import-extractors.d.ts +2 -9
- package/dist/indexer/imports/text-import-extractors.d.ts.map +1 -1
- package/dist/indexer/imports/text-import-extractors.js +0 -284
- package/dist/indexer/imports/text-import-extractors.js.map +1 -1
- package/dist/indexer/imports.d.ts +15 -0
- package/dist/indexer/imports.d.ts.map +1 -1
- package/dist/indexer/imports.js +82 -44
- package/dist/indexer/imports.js.map +1 -1
- package/dist/indexer/incremental-plan.d.ts +0 -1
- package/dist/indexer/incremental-plan.d.ts.map +1 -1
- package/dist/indexer/incremental-plan.js +6 -12
- package/dist/indexer/incremental-plan.js.map +1 -1
- package/dist/indexer/locals-and-exports.d.ts.map +1 -1
- package/dist/indexer/locals-and-exports.js +8 -0
- package/dist/indexer/locals-and-exports.js.map +1 -1
- package/dist/indexer/name-lookup-policies/implicit-self.d.ts.map +1 -1
- package/dist/indexer/name-lookup-policies/implicit-self.js +25 -4
- package/dist/indexer/name-lookup-policies/implicit-self.js.map +1 -1
- package/dist/indexer/name-resolution-types.d.ts +5 -0
- package/dist/indexer/name-resolution-types.d.ts.map +1 -1
- package/dist/indexer/name-resolution.d.ts.map +1 -1
- package/dist/indexer/name-resolution.js +2 -0
- package/dist/indexer/name-resolution.js.map +1 -1
- package/dist/indexer/navigation-cpp.d.ts.map +1 -1
- package/dist/indexer/navigation-cpp.js +3 -5
- package/dist/indexer/navigation-cpp.js.map +1 -1
- package/dist/indexer/navigation-goto.d.ts +49 -3
- package/dist/indexer/navigation-goto.d.ts.map +1 -1
- package/dist/indexer/navigation-goto.js +500 -106
- package/dist/indexer/navigation-goto.js.map +1 -1
- package/dist/indexer/navigation-local.d.ts +7 -0
- package/dist/indexer/navigation-local.d.ts.map +1 -1
- package/dist/indexer/navigation-local.js +22 -2
- package/dist/indexer/navigation-local.js.map +1 -1
- package/dist/indexer/navigation-php.d.ts +0 -1
- package/dist/indexer/navigation-php.d.ts.map +1 -1
- package/dist/indexer/navigation-php.js +1 -7
- package/dist/indexer/navigation-php.js.map +1 -1
- package/dist/indexer/navigation-references.d.ts +1 -0
- package/dist/indexer/navigation-references.d.ts.map +1 -1
- package/dist/indexer/navigation-references.js +134 -23
- package/dist/indexer/navigation-references.js.map +1 -1
- package/dist/indexer/navigation-resolve.d.ts +22 -0
- package/dist/indexer/navigation-resolve.d.ts.map +1 -1
- package/dist/indexer/navigation-resolve.js +73 -8
- package/dist/indexer/navigation-resolve.js.map +1 -1
- package/dist/indexer/navigation.d.ts +4 -1
- package/dist/indexer/navigation.d.ts.map +1 -1
- package/dist/indexer/navigation.js +429 -109
- package/dist/indexer/navigation.js.map +1 -1
- package/dist/indexer/python-property-decorator.d.ts +11 -0
- package/dist/indexer/python-property-decorator.d.ts.map +1 -0
- package/dist/indexer/python-property-decorator.js +30 -0
- package/dist/indexer/python-property-decorator.js.map +1 -0
- package/dist/indexer/reference-context.d.ts.map +1 -1
- package/dist/indexer/reference-context.js +4 -1
- package/dist/indexer/reference-context.js.map +1 -1
- package/dist/indexer/scope-nodes.d.ts.map +1 -1
- package/dist/indexer/scope-nodes.js +5 -0
- package/dist/indexer/scope-nodes.js.map +1 -1
- package/dist/indexer/shared-owner-identity.d.ts +3 -1
- package/dist/indexer/shared-owner-identity.d.ts.map +1 -1
- package/dist/indexer/shared-owner-identity.js +40 -12
- package/dist/indexer/shared-owner-identity.js.map +1 -1
- package/dist/indexer/shared.d.ts +10 -0
- package/dist/indexer/shared.d.ts.map +1 -1
- package/dist/indexer/shared.js +8 -0
- package/dist/indexer/shared.js.map +1 -1
- package/dist/indexer/star-import-precedence.d.ts +5 -1
- package/dist/indexer/star-import-precedence.d.ts.map +1 -1
- package/dist/indexer/star-import-precedence.js +12 -19
- package/dist/indexer/star-import-precedence.js.map +1 -1
- package/dist/indexer/type-hierarchy.d.ts.map +1 -1
- package/dist/indexer/type-hierarchy.js +8 -9
- package/dist/indexer/type-hierarchy.js.map +1 -1
- package/dist/indexer/types.d.ts +54 -2
- package/dist/indexer/types.d.ts.map +1 -1
- package/dist/indexer/types.js +4 -0
- package/dist/indexer/types.js.map +1 -1
- package/dist/indexer/workspace-symbols.d.ts.map +1 -1
- package/dist/indexer/workspace-symbols.js +3 -13
- package/dist/indexer/workspace-symbols.js.map +1 -1
- package/dist/installer/locks.d.ts +1 -0
- package/dist/installer/locks.d.ts.map +1 -1
- package/dist/installer/locks.js +4 -8
- package/dist/installer/locks.js.map +1 -1
- package/dist/installer/registry.d.ts.map +1 -1
- package/dist/installer/registry.js +9 -15
- package/dist/installer/registry.js.map +1 -1
- package/dist/languages/all.d.ts +1 -0
- package/dist/languages/all.d.ts.map +1 -1
- package/dist/languages/all.js +1 -0
- package/dist/languages/all.js.map +1 -1
- package/dist/languages/callable-arity.d.ts.map +1 -1
- package/dist/languages/callable-arity.js +1 -38
- package/dist/languages/callable-arity.js.map +1 -1
- package/dist/languages/definitions/csharp.d.ts.map +1 -1
- package/dist/languages/definitions/csharp.js +5 -13
- package/dist/languages/definitions/csharp.js.map +1 -1
- package/dist/languages/definitions/java.d.ts.map +1 -1
- package/dist/languages/definitions/java.js +4 -13
- package/dist/languages/definitions/java.js.map +1 -1
- package/dist/languages/definitions/kotlin.d.ts.map +1 -1
- package/dist/languages/definitions/kotlin.js +4 -8
- package/dist/languages/definitions/kotlin.js.map +1 -1
- package/dist/languages/definitions/python.d.ts.map +1 -1
- package/dist/languages/definitions/python.js +5 -3
- package/dist/languages/definitions/python.js.map +1 -1
- package/dist/languages/definitions/terraform.d.ts +9 -0
- package/dist/languages/definitions/terraform.d.ts.map +1 -0
- package/dist/languages/definitions/terraform.js +84 -0
- package/dist/languages/definitions/terraform.js.map +1 -0
- package/dist/languages/import-statement-parsers.d.ts +0 -30
- package/dist/languages/import-statement-parsers.d.ts.map +1 -1
- package/dist/languages/import-statement-parsers.js +1 -81
- package/dist/languages/import-statement-parsers.js.map +1 -1
- package/dist/languages/sfc.d.ts.map +1 -1
- package/dist/languages/sfc.js +8 -43
- package/dist/languages/sfc.js.map +1 -1
- package/dist/languages.d.ts +1 -0
- package/dist/languages.d.ts.map +1 -1
- package/dist/languages.js +1 -0
- package/dist/languages.js.map +1 -1
- package/dist/lifecycle/manifest.d.ts.map +1 -1
- package/dist/lifecycle/manifest.js +11 -29
- package/dist/lifecycle/manifest.js.map +1 -1
- package/dist/mcp/handlers.d.ts +21 -0
- package/dist/mcp/handlers.d.ts.map +1 -1
- package/dist/mcp/handlers.js +66 -20
- package/dist/mcp/handlers.js.map +1 -1
- package/dist/mcp/http-transport.d.ts.map +1 -1
- package/dist/mcp/http-transport.js +5 -1
- package/dist/mcp/http-transport.js.map +1 -1
- package/dist/mcp/project-watcher.d.ts +125 -0
- package/dist/mcp/project-watcher.d.ts.map +1 -0
- package/dist/mcp/project-watcher.js +643 -0
- package/dist/mcp/project-watcher.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +5 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/native/byte-index.d.ts +1 -0
- package/dist/native/byte-index.d.ts.map +1 -1
- package/dist/native/byte-index.js +1 -1
- package/dist/native/byte-index.js.map +1 -1
- package/dist/native/contracts.d.ts +1 -0
- package/dist/native/contracts.d.ts.map +1 -1
- package/dist/native/native-backend-report.d.ts +2 -1
- package/dist/native/native-backend-report.d.ts.map +1 -1
- package/dist/native/native-backend-report.js +23 -0
- package/dist/native/native-backend-report.js.map +1 -1
- package/dist/native/tree-shape.d.ts +5 -7
- package/dist/native/tree-shape.d.ts.map +1 -1
- package/dist/native/tree-shape.js +11 -10
- package/dist/native/tree-shape.js.map +1 -1
- package/dist/presentation/bounds.d.ts +2 -0
- package/dist/presentation/bounds.d.ts.map +1 -1
- package/dist/presentation/bounds.js +8 -0
- package/dist/presentation/bounds.js.map +1 -1
- package/dist/review/candidates.d.ts.map +1 -1
- package/dist/review/candidates.js +4 -25
- package/dist/review/candidates.js.map +1 -1
- package/dist/review/report.d.ts.map +1 -1
- package/dist/review/report.js +4 -2
- package/dist/review/report.js.map +1 -1
- package/dist/review/summaries.d.ts.map +1 -1
- package/dist/review/summaries.js +14 -35
- package/dist/review/summaries.js.map +1 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +4 -5
- package/dist/review.js.map +1 -1
- package/dist/scss/navigation.d.ts +13 -0
- package/dist/scss/navigation.d.ts.map +1 -0
- package/dist/scss/navigation.js +150 -0
- package/dist/scss/navigation.js.map +1 -0
- package/dist/scss/resolution.d.ts +190 -0
- package/dist/scss/resolution.d.ts.map +1 -0
- package/dist/scss/resolution.js +1802 -0
- package/dist/scss/resolution.js.map +1 -0
- package/dist/scss/syntax.d.ts +181 -0
- package/dist/scss/syntax.d.ts.map +1 -0
- package/dist/scss/syntax.js +1436 -0
- package/dist/scss/syntax.js.map +1 -0
- package/dist/sql/classify-sql-file.d.ts +7 -0
- package/dist/sql/classify-sql-file.d.ts.map +1 -1
- package/dist/sql/classify-sql-file.js +9 -0
- package/dist/sql/classify-sql-file.js.map +1 -1
- package/dist/sql/extract-facts.d.ts.map +1 -1
- package/dist/sql/extract-facts.js +517 -77
- package/dist/sql/extract-facts.js.map +1 -1
- package/dist/sql/lex.d.ts +100 -1
- package/dist/sql/lex.d.ts.map +1 -1
- package/dist/sql/lex.js +473 -11
- package/dist/sql/lex.js.map +1 -1
- package/dist/sql/lookup.d.ts +17 -0
- package/dist/sql/lookup.d.ts.map +1 -1
- package/dist/sql/lookup.js +21 -0
- package/dist/sql/lookup.js.map +1 -1
- package/dist/sql/navigation.d.ts +28 -1
- package/dist/sql/navigation.d.ts.map +1 -1
- package/dist/sql/navigation.js +474 -84
- package/dist/sql/navigation.js.map +1 -1
- package/dist/sql/object-mentions.d.ts +24 -0
- package/dist/sql/object-mentions.d.ts.map +1 -0
- package/dist/sql/object-mentions.js +233 -0
- package/dist/sql/object-mentions.js.map +1 -0
- package/dist/sql/review.d.ts +3 -0
- package/dist/sql/review.d.ts.map +1 -1
- package/dist/sql/review.js +15 -14
- package/dist/sql/review.js.map +1 -1
- package/dist/sql/routine-mentions.d.ts +61 -0
- package/dist/sql/routine-mentions.d.ts.map +1 -0
- package/dist/sql/routine-mentions.js +868 -0
- package/dist/sql/routine-mentions.js.map +1 -0
- package/dist/sql/source-graph.d.ts +18 -3
- package/dist/sql/source-graph.d.ts.map +1 -1
- package/dist/sql/source-graph.js +109 -32
- package/dist/sql/source-graph.js.map +1 -1
- package/dist/sqlite/raw-query-worker-pool.d.ts.map +1 -1
- package/dist/sqlite/raw-query-worker-pool.js +2 -0
- package/dist/sqlite/raw-query-worker-pool.js.map +1 -1
- package/dist/sqlite/types.d.ts +6 -5
- package/dist/sqlite/types.d.ts.map +1 -1
- package/dist/sqlite/write.d.ts.map +1 -1
- package/dist/sqlite/write.js.map +1 -1
- package/dist/sqlite-driver.d.ts +0 -1
- package/dist/sqlite-driver.d.ts.map +1 -1
- package/dist/sqlite-driver.js +0 -11
- package/dist/sqlite-driver.js.map +1 -1
- package/dist/terraform/navigation.d.ts +20 -0
- package/dist/terraform/navigation.d.ts.map +1 -0
- package/dist/terraform/navigation.js +140 -0
- package/dist/terraform/navigation.js.map +1 -0
- package/dist/terraform/resolution.d.ts +82 -0
- package/dist/terraform/resolution.d.ts.map +1 -0
- package/dist/terraform/resolution.js +324 -0
- package/dist/terraform/resolution.js.map +1 -0
- package/dist/terraform/syntax.d.ts +109 -0
- package/dist/terraform/syntax.d.ts.map +1 -0
- package/dist/terraform/syntax.js +438 -0
- package/dist/terraform/syntax.js.map +1 -0
- package/dist/util/ast.d.ts +5 -0
- package/dist/util/ast.d.ts.map +1 -1
- package/dist/util/ast.js +10 -0
- package/dist/util/ast.js.map +1 -1
- package/dist/util/atomic-file.d.ts +22 -0
- package/dist/util/atomic-file.d.ts.map +1 -0
- package/dist/util/atomic-file.js +106 -0
- package/dist/util/atomic-file.js.map +1 -0
- package/dist/util/collections.d.ts +5 -0
- package/dist/util/collections.d.ts.map +1 -1
- package/dist/util/collections.js +26 -0
- package/dist/util/collections.js.map +1 -1
- package/dist/util/concurrency.d.ts +1 -0
- package/dist/util/concurrency.d.ts.map +1 -1
- package/dist/util/concurrency.js +5 -0
- package/dist/util/concurrency.js.map +1 -1
- package/dist/util/confined-file.d.ts.map +1 -1
- package/dist/util/confined-file.js +1 -3
- package/dist/util/confined-file.js.map +1 -1
- package/dist/util/csharp-tree.d.ts +65 -0
- package/dist/util/csharp-tree.d.ts.map +1 -0
- package/dist/util/csharp-tree.js +266 -0
- package/dist/util/csharp-tree.js.map +1 -0
- package/dist/util/errors.d.ts +1 -0
- package/dist/util/errors.d.ts.map +1 -1
- package/dist/util/errors.js +3 -0
- package/dist/util/errors.js.map +1 -1
- package/dist/util/graph-edges.d.ts +2 -0
- package/dist/util/graph-edges.d.ts.map +1 -1
- package/dist/util/graph-edges.js +11 -0
- package/dist/util/graph-edges.js.map +1 -1
- package/dist/util/guards.d.ts +1 -0
- package/dist/util/guards.d.ts.map +1 -1
- package/dist/util/guards.js +6 -0
- package/dist/util/guards.js.map +1 -1
- package/dist/util/identifiers.d.ts +0 -24
- package/dist/util/identifiers.d.ts.map +1 -1
- package/dist/util/identifiers.js +0 -24
- package/dist/util/identifiers.js.map +1 -1
- package/dist/util/jvm-tree.d.ts +58 -0
- package/dist/util/jvm-tree.d.ts.map +1 -0
- package/dist/util/jvm-tree.js +153 -0
- package/dist/util/jvm-tree.js.map +1 -0
- package/dist/util/member-access-tables.d.ts.map +1 -1
- package/dist/util/member-access-tables.js +3 -0
- package/dist/util/member-access-tables.js.map +1 -1
- package/dist/util/package-exports.d.ts +0 -22
- package/dist/util/package-exports.d.ts.map +1 -1
- package/dist/util/package-exports.js +25 -5
- package/dist/util/package-exports.js.map +1 -1
- package/dist/util/paths.d.ts +1 -0
- package/dist/util/paths.d.ts.map +1 -1
- package/dist/util/paths.js +10 -4
- package/dist/util/paths.js.map +1 -1
- package/dist/util/project-files.d.ts.map +1 -1
- package/dist/util/project-files.js +3 -1
- package/dist/util/project-files.js.map +1 -1
- package/dist/util/resolution/csharp.d.ts +20 -8
- package/dist/util/resolution/csharp.d.ts.map +1 -1
- package/dist/util/resolution/csharp.js +126 -127
- package/dist/util/resolution/csharp.js.map +1 -1
- package/dist/util/resolution/files.d.ts +6 -0
- package/dist/util/resolution/files.d.ts.map +1 -1
- package/dist/util/resolution/files.js +74 -21
- package/dist/util/resolution/files.js.map +1 -1
- package/dist/util/resolution/jvm.d.ts +21 -1
- package/dist/util/resolution/jvm.d.ts.map +1 -1
- package/dist/util/resolution/jvm.js +168 -100
- package/dist/util/resolution/jvm.js.map +1 -1
- package/dist/util/resolution/project-symbols.d.ts +1 -1
- package/dist/util/resolution/project-symbols.d.ts.map +1 -1
- package/dist/util/resolution/project-symbols.js.map +1 -1
- package/dist/util/resolution/specifier-targets.d.ts +4 -0
- package/dist/util/resolution/specifier-targets.d.ts.map +1 -1
- package/dist/util/resolution/specifier-targets.js +16 -3
- package/dist/util/resolution/specifier-targets.js.map +1 -1
- package/dist/util/resolution/terraform.d.ts +18 -0
- package/dist/util/resolution/terraform.d.ts.map +1 -0
- package/dist/util/resolution/terraform.js +64 -0
- package/dist/util/resolution/terraform.js.map +1 -0
- package/dist/util/resolution.d.ts.map +1 -1
- package/dist/util/resolution.js +21 -7
- package/dist/util/resolution.js.map +1 -1
- package/dist/util/syntax-tree.d.ts +16 -0
- package/dist/util/syntax-tree.d.ts.map +1 -0
- package/dist/util/syntax-tree.js +34 -0
- package/dist/util/syntax-tree.js.map +1 -0
- package/dist/util/text-scanning.d.ts +6 -0
- package/dist/util/text-scanning.d.ts.map +1 -0
- package/dist/util/text-scanning.js +35 -0
- package/dist/util/text-scanning.js.map +1 -0
- package/dist/util/trivia-tables.d.ts +13 -8
- package/dist/util/trivia-tables.d.ts.map +1 -1
- package/dist/util/trivia-tables.js +13 -63
- package/dist/util/trivia-tables.js.map +1 -1
- package/dist/util/trivia.d.ts +10 -3
- package/dist/util/trivia.d.ts.map +1 -1
- package/dist/util/trivia.js +209 -79
- package/dist/util/trivia.js.map +1 -1
- package/dist/util/windows-process-drain.d.ts.map +1 -1
- package/dist/util/windows-process-drain.js.map +1 -1
- package/dist/util/worker-threads.d.ts +16 -0
- package/dist/util/worker-threads.d.ts.map +1 -1
- package/dist/util/worker-threads.js +118 -0
- package/dist/util/worker-threads.js.map +1 -1
- package/dist/worker/native-worker-pool.d.ts.map +1 -1
- package/dist/worker/native-worker-pool.js +2 -1
- package/dist/worker/native-worker-pool.js.map +1 -1
- package/docs/mcp.md +27 -2
- package/package.json +8 -8
|
@@ -0,0 +1,1802 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ensureParsedContext } from "../indexer/parse-context.js";
|
|
3
|
+
import { sameDef } from "../indexer/reference-context.js";
|
|
4
|
+
import { SymbolKind } from "../indexer/types.js";
|
|
5
|
+
import { supportForFileWithoutHeaderSample } from "../languages.js";
|
|
6
|
+
import { isNativeRequiredUnavailableError } from "../native/runtime.js";
|
|
7
|
+
import { fileIdentityKey } from "../util/paths.js";
|
|
8
|
+
import { dynamicFormOf, extractScssFacts, isSassBuiltinUrl, memberEditSpan, moduleStem, sassBare, sassCanonical, SCSS_LANGUAGE_ID, scssOffsetAt, scssOffsetRange, } from "./syntax.js";
|
|
9
|
+
const HOST_EXTENSIONS = [".vue", ".svelte"];
|
|
10
|
+
const factsByModule = new WeakMap();
|
|
11
|
+
const projects = new WeakMap();
|
|
12
|
+
export function isScssFile(index, file) {
|
|
13
|
+
return supportForFileWithoutHeaderSample(file, index.languageExtensions)?.id === SCSS_LANGUAGE_ID;
|
|
14
|
+
}
|
|
15
|
+
async function computeFacts(index, module) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = await ensureParsedContext(module.file, index.parsed?.get(fileIdentityKey(module.file)), index.languageExtensions);
|
|
18
|
+
if (parsed.sup.id !== SCSS_LANGUAGE_ID)
|
|
19
|
+
return null;
|
|
20
|
+
return extractScssFacts(parsed.source);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (isNativeRequiredUnavailableError(error))
|
|
24
|
+
throw error;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Declarations, loads, and uses of one indexed SCSS file, or null when it cannot be read. */
|
|
29
|
+
export function scssFactsForFile(index, file) {
|
|
30
|
+
const module = index.byFile.get(fileIdentityKey(file));
|
|
31
|
+
if (!module || !isScssFile(index, module.file))
|
|
32
|
+
return Promise.resolve(null);
|
|
33
|
+
let facts = factsByModule.get(module);
|
|
34
|
+
if (!facts) {
|
|
35
|
+
facts = computeFacts(index, module);
|
|
36
|
+
factsByModule.set(module, facts);
|
|
37
|
+
}
|
|
38
|
+
return facts;
|
|
39
|
+
}
|
|
40
|
+
function defKey(line, column, canonical) {
|
|
41
|
+
return `${line}:${column}:${canonical}`;
|
|
42
|
+
}
|
|
43
|
+
function isPrivateName(canonical) {
|
|
44
|
+
// Variables, mixins, and functions fold `_` into `-`; a placeholder keeps the spelling it has.
|
|
45
|
+
return canonical.startsWith("-") || canonical.startsWith("_");
|
|
46
|
+
}
|
|
47
|
+
function appendTo(table, key, value) {
|
|
48
|
+
const existing = table.get(key);
|
|
49
|
+
if (existing)
|
|
50
|
+
existing.push(value);
|
|
51
|
+
else
|
|
52
|
+
table.set(key, [value]);
|
|
53
|
+
}
|
|
54
|
+
function hasHostExtension(file) {
|
|
55
|
+
const lower = file.toLowerCase();
|
|
56
|
+
return HOST_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Where each stylesheet URL of an SCSS file points, by `fileKey NUL url`. One pass over the graph
|
|
60
|
+
* edges replaces a scan of every edge for every load.
|
|
61
|
+
*/
|
|
62
|
+
function stylesheetTargets(index, scssKeys) {
|
|
63
|
+
const targets = new Map();
|
|
64
|
+
for (const edge of index.graph.edges) {
|
|
65
|
+
if (edge.to.type !== "file")
|
|
66
|
+
continue;
|
|
67
|
+
const key = fileIdentityKey(edge.from);
|
|
68
|
+
if (!scssKeys.has(key))
|
|
69
|
+
continue;
|
|
70
|
+
const entry = `${key}\0${edge.raw}`;
|
|
71
|
+
if (!targets.has(entry))
|
|
72
|
+
targets.set(entry, edge.to.path);
|
|
73
|
+
}
|
|
74
|
+
return targets;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Vue and Svelte files import stylesheets from `<style>` blocks that SCSS navigation does not
|
|
78
|
+
* read. Their imports are kept by target when a resolver proved one and by stem when not.
|
|
79
|
+
*/
|
|
80
|
+
function hostImports(index) {
|
|
81
|
+
const byStem = new Map();
|
|
82
|
+
const byTarget = new Map();
|
|
83
|
+
const add = (table, key, host) => {
|
|
84
|
+
const files = table.get(key);
|
|
85
|
+
if (!files)
|
|
86
|
+
table.set(key, [host]);
|
|
87
|
+
else if (!files.includes(host))
|
|
88
|
+
files.push(host);
|
|
89
|
+
};
|
|
90
|
+
for (const edge of index.graph.edges) {
|
|
91
|
+
if (!hasHostExtension(edge.from))
|
|
92
|
+
continue;
|
|
93
|
+
if (edge.to.type === "file")
|
|
94
|
+
add(byTarget, fileIdentityKey(edge.to.path), edge.from);
|
|
95
|
+
else
|
|
96
|
+
add(byStem, moduleStem(edge.raw), edge.from);
|
|
97
|
+
}
|
|
98
|
+
return { byStem, byTarget };
|
|
99
|
+
}
|
|
100
|
+
function indexedFile(index, candidate) {
|
|
101
|
+
return index.byFile.get(fileIdentityKey(candidate))?.file;
|
|
102
|
+
}
|
|
103
|
+
function importUrlLocation(fromFile, url) {
|
|
104
|
+
const trimmed = url.trim();
|
|
105
|
+
const bare = (trimmed.split(/[?#]/u)[0] ?? "").trim();
|
|
106
|
+
if (!bare || isSassBuiltinUrl(trimmed))
|
|
107
|
+
return null;
|
|
108
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(bare) && !/^[a-z]:[\/]/i.test(bare))
|
|
109
|
+
return null;
|
|
110
|
+
const resolved = path.resolve(path.dirname(fromFile), bare);
|
|
111
|
+
const extension = path.extname(resolved).toLowerCase();
|
|
112
|
+
const hasStyleExtension = extension === ".scss" || extension === ".sass" || extension === ".css";
|
|
113
|
+
let stemPath = hasStyleExtension ? resolved.slice(0, -extension.length) : resolved;
|
|
114
|
+
if (stemPath.toLowerCase().endsWith(".import"))
|
|
115
|
+
stemPath = stemPath.slice(0, -".import".length);
|
|
116
|
+
let stem = path.basename(stemPath);
|
|
117
|
+
if (stem.startsWith("_"))
|
|
118
|
+
stem = stem.slice(1);
|
|
119
|
+
return { base: resolved, dir: path.dirname(stemPath), stem, hasStyleExtension };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* One tier of import-only files `@import` tries for `url`.
|
|
123
|
+
*
|
|
124
|
+
* The order is file import-only sibling, then an ordinary file, then a directory import-only
|
|
125
|
+
* index, then a directory index. A directory `_index.import.scss` must not hide `lib.scss`.
|
|
126
|
+
* `@use` and `@forward` never take this path. Two siblings in one tier are ambiguous.
|
|
127
|
+
*/
|
|
128
|
+
function importOnlyTarget(index, fromFile, url, tier) {
|
|
129
|
+
const located = importUrlLocation(fromFile, url);
|
|
130
|
+
if (!located || !located.stem)
|
|
131
|
+
return { status: "none" };
|
|
132
|
+
const pick = (candidates) => {
|
|
133
|
+
const found = candidates
|
|
134
|
+
.map((candidate) => indexedFile(index, candidate))
|
|
135
|
+
.filter((file) => file !== undefined);
|
|
136
|
+
const unique = [...new Map(found.map((file) => [fileIdentityKey(file), file])).values()];
|
|
137
|
+
if (unique.length > 1)
|
|
138
|
+
return { status: "ambiguous", stems: unique.map((file) => moduleStem(file)) };
|
|
139
|
+
const [only] = unique;
|
|
140
|
+
return only ? { status: "one", file: only } : null;
|
|
141
|
+
};
|
|
142
|
+
if (tier === "file") {
|
|
143
|
+
return (pick([
|
|
144
|
+
path.join(located.dir, `_${located.stem}.import.scss`),
|
|
145
|
+
path.join(located.dir, `${located.stem}.import.scss`),
|
|
146
|
+
]) ?? { status: "none" });
|
|
147
|
+
}
|
|
148
|
+
if (located.hasStyleExtension)
|
|
149
|
+
return { status: "none" };
|
|
150
|
+
return (pick([path.join(located.base, "_index.import.scss"), path.join(located.base, "index.import.scss")]) ?? {
|
|
151
|
+
status: "none",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** True when `target` is the directory index of `url`, not a stylesheet file of that name. */
|
|
155
|
+
function isDirectoryIndexFile(fromFile, url, target) {
|
|
156
|
+
const located = importUrlLocation(fromFile, url);
|
|
157
|
+
if (!located || located.hasStyleExtension || !located.stem)
|
|
158
|
+
return false;
|
|
159
|
+
const rel = path.relative(located.base, path.resolve(target)).replace(/\\/g, "/");
|
|
160
|
+
return /^_?index\.(scss|sass|css)$/i.test(rel);
|
|
161
|
+
}
|
|
162
|
+
function loadTarget(targets, importTargets, key, url, kind, fromFile, index) {
|
|
163
|
+
const trimmed = url.trim();
|
|
164
|
+
if (isSassBuiltinUrl(trimmed))
|
|
165
|
+
return { type: "builtin" };
|
|
166
|
+
const edge = targets.get(`${key}\0${trimmed}`) ?? importTargets.get(trimmed);
|
|
167
|
+
// `@import` prefers an import-only file. `@use` and `@forward` keep the ordinary edge.
|
|
168
|
+
if (kind === "import") {
|
|
169
|
+
const fileLevel = importOnlyTarget(index, fromFile, trimmed, "file");
|
|
170
|
+
if (fileLevel.status === "one")
|
|
171
|
+
return { type: "file", file: fileLevel.file };
|
|
172
|
+
if (fileLevel.status === "ambiguous")
|
|
173
|
+
return { type: "unresolved", extraStems: fileLevel.stems };
|
|
174
|
+
// A directory import-only index must not override an ordinary file such as lib.scss.
|
|
175
|
+
if (edge && !isDirectoryIndexFile(fromFile, trimmed, edge))
|
|
176
|
+
return { type: "file", file: edge };
|
|
177
|
+
const directoryLevel = importOnlyTarget(index, fromFile, trimmed, "directory");
|
|
178
|
+
if (directoryLevel.status === "one")
|
|
179
|
+
return { type: "file", file: directoryLevel.file };
|
|
180
|
+
if (directoryLevel.status === "ambiguous")
|
|
181
|
+
return { type: "unresolved", extraStems: directoryLevel.stems };
|
|
182
|
+
}
|
|
183
|
+
if (edge)
|
|
184
|
+
return { type: "file", file: edge };
|
|
185
|
+
return { type: "unresolved" };
|
|
186
|
+
}
|
|
187
|
+
function buildOwnMembers(file, facts, defAt) {
|
|
188
|
+
const own = [];
|
|
189
|
+
const ownByName = new Map();
|
|
190
|
+
const variables = new Map();
|
|
191
|
+
const placeholders = [];
|
|
192
|
+
const placeholderSites = new Map();
|
|
193
|
+
for (const declaration of facts.declarations) {
|
|
194
|
+
if (!declaration.topLevel)
|
|
195
|
+
continue;
|
|
196
|
+
// The key matches `defAt`: the position of the definition and its name with `_` folded into `-`.
|
|
197
|
+
const siteKey = defKey(declaration.range.start.line, declaration.range.start.column, sassCanonical(declaration.name));
|
|
198
|
+
const def = defAt.get(siteKey);
|
|
199
|
+
if (declaration.kind === "placeholder") {
|
|
200
|
+
appendTo(placeholderSites, declaration.canonical, { def, siteKey, range: declaration.range });
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const name = `${declaration.kind}:${declaration.canonical}`;
|
|
204
|
+
const assignedBefore = declaration.kind === "variable" ? variables.get(name) : undefined;
|
|
205
|
+
if (assignedBefore) {
|
|
206
|
+
// One file assigning a variable again is still one variable. The assignment is a site even
|
|
207
|
+
// when the index holds no definition for it, as for an assignment inside `@each`: a rename
|
|
208
|
+
// that skipped it would leave the old name behind.
|
|
209
|
+
assignedBefore.sites.push({ file, range: declaration.range });
|
|
210
|
+
assignedBefore.siteKeys.add(siteKey);
|
|
211
|
+
// `$x: null; $x: 1 !default` is configurable: `with` is legal when any assignment uses `!default`.
|
|
212
|
+
if (declaration.isDefault)
|
|
213
|
+
assignedBefore.isDefault = true;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (!def)
|
|
217
|
+
continue;
|
|
218
|
+
const member = {
|
|
219
|
+
kind: declaration.kind,
|
|
220
|
+
declared: declaration.canonical,
|
|
221
|
+
def,
|
|
222
|
+
sites: [{ file: def.file, range: def.range }],
|
|
223
|
+
siteKeys: new Set([siteKey]),
|
|
224
|
+
isDefault: declaration.isDefault,
|
|
225
|
+
};
|
|
226
|
+
if (declaration.kind === "variable")
|
|
227
|
+
variables.set(name, member);
|
|
228
|
+
own.push(member);
|
|
229
|
+
appendTo(ownByName, name, member);
|
|
230
|
+
}
|
|
231
|
+
// Sass extends every rule of a file that has the name, so the declarations of one name are one
|
|
232
|
+
// placeholder. Each of them is a site, also when the index holds no definition for it: a rename
|
|
233
|
+
// that skipped one would leave the old name behind.
|
|
234
|
+
for (const [declared, entries] of placeholderSites) {
|
|
235
|
+
const first = entries.find((entry) => entry.def);
|
|
236
|
+
if (!first?.def)
|
|
237
|
+
continue;
|
|
238
|
+
placeholders.push({
|
|
239
|
+
def: first.def,
|
|
240
|
+
declared,
|
|
241
|
+
sites: [
|
|
242
|
+
{ file: first.def.file, range: first.def.range },
|
|
243
|
+
...entries.filter((entry) => entry !== first).map((entry) => ({ file, range: entry.range })),
|
|
244
|
+
],
|
|
245
|
+
siteKeys: new Set(entries.map((entry) => entry.siteKey)),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return { own, ownByName, placeholders };
|
|
249
|
+
}
|
|
250
|
+
function detachFlowLocal(current, local) {
|
|
251
|
+
const list = current.localsByName.get(local.canonical);
|
|
252
|
+
if (list) {
|
|
253
|
+
const next = list.filter((entry) => entry !== local);
|
|
254
|
+
if (next.length)
|
|
255
|
+
current.localsByName.set(local.canonical, next);
|
|
256
|
+
else
|
|
257
|
+
current.localsByName.delete(local.canonical);
|
|
258
|
+
}
|
|
259
|
+
const index = current.facts.locals.indexOf(local);
|
|
260
|
+
if (index >= 0)
|
|
261
|
+
current.facts.locals.splice(index, 1);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* A file-scope flow assignment after `@import` updates the imported variable when exactly one
|
|
265
|
+
* variable of that name is in scope. Several matches, or an import that did not resolve, leave
|
|
266
|
+
* the name unproven instead of inventing a local. No match keeps the block local.
|
|
267
|
+
*/
|
|
268
|
+
function joinImportedFlowAssignments(model) {
|
|
269
|
+
for (const current of model.files.values()) {
|
|
270
|
+
for (const assignment of current.facts.flowAssignments) {
|
|
271
|
+
const found = [];
|
|
272
|
+
let incomplete = false;
|
|
273
|
+
for (const resolved of current.loads) {
|
|
274
|
+
if (resolved.load.kind !== "import" || resolved.load.nested)
|
|
275
|
+
continue;
|
|
276
|
+
const assignmentAt = assignment.range.start.index;
|
|
277
|
+
if (assignmentAt !== undefined && resolved.load.at > assignmentAt)
|
|
278
|
+
continue;
|
|
279
|
+
if (resolved.target.type !== "file") {
|
|
280
|
+
incomplete = true;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const inner = exposureOf(model, fileIdentityKey(resolved.target.file), "import", newWalk());
|
|
284
|
+
if (inner.incomplete)
|
|
285
|
+
incomplete = true;
|
|
286
|
+
found.push(...lookup(inner.byName, "variable", assignment.canonical));
|
|
287
|
+
}
|
|
288
|
+
const matches = uniqueMembers(found);
|
|
289
|
+
if (!incomplete && matches.length === 0)
|
|
290
|
+
continue;
|
|
291
|
+
detachFlowLocal(current, assignment.local);
|
|
292
|
+
if (incomplete || matches.length !== 1) {
|
|
293
|
+
current.unprovenFlowAssigns.add(assignment.canonical);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const member = matches[0];
|
|
297
|
+
const owner = model.files.get(fileIdentityKey(member.def.file));
|
|
298
|
+
const own = owner?.own.find((entry) => entry.def === member.def);
|
|
299
|
+
if (!own) {
|
|
300
|
+
current.unprovenFlowAssigns.add(assignment.canonical);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
own.sites.push({ file: current.file, range: assignment.range });
|
|
304
|
+
current.joinedAssignments.push({ range: assignment.range, member: own });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function memberDeclaredAfter(current, use, member) {
|
|
309
|
+
if (member.kind !== "function" && member.kind !== "mixin")
|
|
310
|
+
return false;
|
|
311
|
+
if (use.kind !== member.kind)
|
|
312
|
+
return false;
|
|
313
|
+
const declaredAt = scssOffsetAt(current.facts, member.def.range.start.line, member.def.range.start.column);
|
|
314
|
+
return declaredAt > use.nameStart;
|
|
315
|
+
}
|
|
316
|
+
async function buildProjectModel(index) {
|
|
317
|
+
const scssModules = [];
|
|
318
|
+
const scssKeys = new Set();
|
|
319
|
+
const unreadable = new Set();
|
|
320
|
+
for (const module of index.byFile.values()) {
|
|
321
|
+
if (!isScssFile(index, module.file))
|
|
322
|
+
continue;
|
|
323
|
+
const facts = await scssFactsForFile(index, module.file);
|
|
324
|
+
if (!facts) {
|
|
325
|
+
unreadable.add(fileIdentityKey(module.file));
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
scssModules.push({ module, facts });
|
|
329
|
+
scssKeys.add(fileIdentityKey(module.file));
|
|
330
|
+
}
|
|
331
|
+
const targets = stylesheetTargets(index, scssKeys);
|
|
332
|
+
const files = new Map();
|
|
333
|
+
const reverse = new Map();
|
|
334
|
+
const importers = new Map();
|
|
335
|
+
for (const { module, facts } of scssModules) {
|
|
336
|
+
const key = fileIdentityKey(module.file);
|
|
337
|
+
const importTargets = new Map();
|
|
338
|
+
const defAt = new Map();
|
|
339
|
+
for (const binding of module.imports) {
|
|
340
|
+
if (typeof binding.resolved === "string" && !importTargets.has(binding.from)) {
|
|
341
|
+
importTargets.set(binding.from, binding.resolved);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const local of module.locals) {
|
|
345
|
+
const at = defKey(local.range.start.line, local.range.start.column, sassCanonical(local.localName));
|
|
346
|
+
if (!defAt.has(at))
|
|
347
|
+
defAt.set(at, local);
|
|
348
|
+
}
|
|
349
|
+
const loads = facts.loads.map((load) => ({
|
|
350
|
+
load,
|
|
351
|
+
target: loadTarget(targets, importTargets, key, load.url, load.kind, module.file, index),
|
|
352
|
+
}));
|
|
353
|
+
const unresolvedStems = new Set();
|
|
354
|
+
for (const resolved of loads) {
|
|
355
|
+
if (resolved.target.type === "file") {
|
|
356
|
+
const targetKey = fileIdentityKey(resolved.target.file);
|
|
357
|
+
appendTo(reverse, targetKey, module.file);
|
|
358
|
+
if (resolved.load.kind === "import" && !importers.get(targetKey)?.includes(key)) {
|
|
359
|
+
appendTo(importers, targetKey, key);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else if (resolved.target.type === "unresolved") {
|
|
363
|
+
unresolvedStems.add(moduleStem(resolved.load.url));
|
|
364
|
+
for (const stem of resolved.target.extraStems ?? [])
|
|
365
|
+
unresolvedStems.add(stem);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const dynamicStems = new Set();
|
|
369
|
+
for (const dynamic of facts.dynamics) {
|
|
370
|
+
if (dynamic.form === "load-css")
|
|
371
|
+
dynamicStems.add(dynamic.arg === null ? null : moduleStem(dynamic.arg));
|
|
372
|
+
}
|
|
373
|
+
const localsByName = new Map();
|
|
374
|
+
for (const local of facts.locals)
|
|
375
|
+
appendTo(localsByName, local.canonical, local);
|
|
376
|
+
const { own, ownByName, placeholders } = buildOwnMembers(module.file, facts, defAt);
|
|
377
|
+
files.set(key, {
|
|
378
|
+
file: module.file,
|
|
379
|
+
key,
|
|
380
|
+
facts,
|
|
381
|
+
loads,
|
|
382
|
+
own,
|
|
383
|
+
ownByName,
|
|
384
|
+
placeholders,
|
|
385
|
+
localsByName,
|
|
386
|
+
defAt,
|
|
387
|
+
unresolvedStems,
|
|
388
|
+
dynamicStems,
|
|
389
|
+
inheritedDynamics: [],
|
|
390
|
+
unprovenFlowAssigns: new Set(),
|
|
391
|
+
joinedAssignments: [],
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
// An SCSS file whose text could not be read still loads other files, as far as the graph knows. It
|
|
395
|
+
// stays a consumer that is never proved, so the coverage of what it loads is partial.
|
|
396
|
+
if (unreadable.size) {
|
|
397
|
+
for (const edge of index.graph.edges) {
|
|
398
|
+
if (edge.to.type === "file" && unreadable.has(fileIdentityKey(edge.from))) {
|
|
399
|
+
appendTo(reverse, fileIdentityKey(edge.to.path), edge.from);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const hosts = hostImports(index);
|
|
404
|
+
const model = {
|
|
405
|
+
files,
|
|
406
|
+
reverse,
|
|
407
|
+
importers,
|
|
408
|
+
importerChains: new Map(),
|
|
409
|
+
exposures: new Map(),
|
|
410
|
+
hostsByStem: hosts.byStem,
|
|
411
|
+
hostsByTarget: hosts.byTarget,
|
|
412
|
+
};
|
|
413
|
+
joinImportedFlowAssignments(model);
|
|
414
|
+
for (const current of files.values()) {
|
|
415
|
+
for (const use of current.facts.uses) {
|
|
416
|
+
if (!use.dynamic)
|
|
417
|
+
continue;
|
|
418
|
+
// A file that `@use`s a module which forwards `sass:meta` sees that module's namespace.
|
|
419
|
+
// The lookup is not inherited: the namespace is declared here, and it is not `sass:meta` itself.
|
|
420
|
+
const proved = inheritsMetaLookup(model, current, use) ||
|
|
421
|
+
(use.namespace !== null && namespaceForwardsSassMeta(model, current, use.namespace));
|
|
422
|
+
if (!proved)
|
|
423
|
+
continue;
|
|
424
|
+
current.inheritedDynamics.push(use.dynamic);
|
|
425
|
+
if (use.dynamic.form === "load-css") {
|
|
426
|
+
current.dynamicStems.add(use.dynamic.arg === null ? null : moduleStem(use.dynamic.arg));
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return model;
|
|
431
|
+
}
|
|
432
|
+
function projectModel(index) {
|
|
433
|
+
let pending = projects.get(index);
|
|
434
|
+
if (!pending) {
|
|
435
|
+
pending = buildProjectModel(index);
|
|
436
|
+
projects.set(index, pending);
|
|
437
|
+
}
|
|
438
|
+
return pending;
|
|
439
|
+
}
|
|
440
|
+
function ownAsMember(member, importOnly) {
|
|
441
|
+
return {
|
|
442
|
+
kind: member.kind,
|
|
443
|
+
exposed: member.declared,
|
|
444
|
+
prefix: "",
|
|
445
|
+
def: member.def,
|
|
446
|
+
isDefault: member.isDefault,
|
|
447
|
+
importOnly,
|
|
448
|
+
filters: [],
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function withPrefix(member, prefix) {
|
|
452
|
+
if (!prefix)
|
|
453
|
+
return member;
|
|
454
|
+
const canonicalPrefix = sassCanonical(prefix);
|
|
455
|
+
return {
|
|
456
|
+
...member,
|
|
457
|
+
exposed: canonicalPrefix + member.exposed,
|
|
458
|
+
prefix: prefix + member.prefix,
|
|
459
|
+
...(member.lensFrom !== undefined ? { lensFrom: canonicalPrefix + member.lensFrom } : {}),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* The member as a module that forwards it exposes it: the prefix is added, and a `show` or `hide`
|
|
464
|
+
* list is kept, so rename preview can test the new name against it.
|
|
465
|
+
*/
|
|
466
|
+
function withForward(member, load) {
|
|
467
|
+
const prefixed = withPrefix(member, load.prefix);
|
|
468
|
+
if (!load.show && !load.hide)
|
|
469
|
+
return prefixed;
|
|
470
|
+
return { ...prefixed, filters: [...member.filters, { load, exposed: prefixed.exposed }] };
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* `@import` runs the imported file inside the scope of the importer. The chain holds every file
|
|
474
|
+
* that imports `key`, directly or through other imports, nearest first, so a partial can read what
|
|
475
|
+
* its importers declared and loaded. `@use` and `@forward` give a file no such scope.
|
|
476
|
+
*/
|
|
477
|
+
function importerChain(model, key) {
|
|
478
|
+
const cached = model.importerChains.get(key);
|
|
479
|
+
if (cached)
|
|
480
|
+
return cached;
|
|
481
|
+
const chain = [];
|
|
482
|
+
const seen = new Set([key]);
|
|
483
|
+
const queue = [key];
|
|
484
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
485
|
+
for (const importerKey of model.importers.get(next) ?? []) {
|
|
486
|
+
if (seen.has(importerKey))
|
|
487
|
+
continue;
|
|
488
|
+
seen.add(importerKey);
|
|
489
|
+
const importer = model.files.get(importerKey);
|
|
490
|
+
if (!importer)
|
|
491
|
+
continue;
|
|
492
|
+
chain.push(importer);
|
|
493
|
+
queue.push(importerKey);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
model.importerChains.set(key, chain);
|
|
497
|
+
return chain;
|
|
498
|
+
}
|
|
499
|
+
function listMatches(list, member, exposed, adjust) {
|
|
500
|
+
return list.some((entry) => {
|
|
501
|
+
const wantsVariable = entry.kind === "variable";
|
|
502
|
+
if (wantsVariable !== (member.kind === "variable"))
|
|
503
|
+
return false;
|
|
504
|
+
const name = sassCanonical(entry.name);
|
|
505
|
+
return (adjust ? adjust(name) : name) === exposed;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* `show` and `hide` list the names a forward exposes, so they compare against the prefixed
|
|
510
|
+
* spelling. A bare name selects mixins and functions, a `$name` selects variables.
|
|
511
|
+
*/
|
|
512
|
+
function passesFilter(member, load) {
|
|
513
|
+
const exposed = sassCanonical(load.prefix) + member.exposed;
|
|
514
|
+
// A list entry that spells the name the member has today is edited with the rename. Every other
|
|
515
|
+
// entry stays, so a `show $y` that did not name `$x` admits `$x` once it is renamed to `$y`.
|
|
516
|
+
const previous = member.lensFrom === undefined ? undefined : sassCanonical(load.prefix) + member.lensFrom;
|
|
517
|
+
const adjust = previous === undefined ? undefined : (name) => (name === previous ? exposed : name);
|
|
518
|
+
if (load.show && !listMatches(load.show, member, exposed, adjust))
|
|
519
|
+
return false;
|
|
520
|
+
if (load.hide && listMatches(load.hide, member, exposed, adjust))
|
|
521
|
+
return false;
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
function finishExposure(members, incomplete) {
|
|
525
|
+
const byName = new Map();
|
|
526
|
+
for (const member of members)
|
|
527
|
+
appendTo(byName, `${member.kind}:${member.exposed}`, member);
|
|
528
|
+
return { members, byName, incomplete };
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Members `file` shows to a consumer. A `module` consumer (`@use`, `@forward`) sees the public
|
|
532
|
+
* declarations, the forwarded modules, and what the file `@import`s. An `@import` consumer sees
|
|
533
|
+
* private declarations too, because `@import` has no privacy. `@use` never passes members on.
|
|
534
|
+
* Results are cached unless a cycle cut the walk short.
|
|
535
|
+
*/
|
|
536
|
+
function applyLens(member, lens) {
|
|
537
|
+
if (!lens || member.def !== lens.def || !member.exposed.endsWith(lens.from))
|
|
538
|
+
return member;
|
|
539
|
+
return {
|
|
540
|
+
...member,
|
|
541
|
+
exposed: member.exposed.slice(0, member.exposed.length - lens.from.length) + lens.to,
|
|
542
|
+
lensFrom: member.exposed,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
function exposureOf(model, key, mode, walk, lens) {
|
|
546
|
+
const cache = lens?.cache ?? model.exposures;
|
|
547
|
+
const cacheKey = `${mode}:${key}`;
|
|
548
|
+
const cached = cache.get(cacheKey);
|
|
549
|
+
if (cached)
|
|
550
|
+
return cached;
|
|
551
|
+
const current = model.files.get(key);
|
|
552
|
+
if (!current)
|
|
553
|
+
return finishExposure([], true);
|
|
554
|
+
if (walk.trail.has(key)) {
|
|
555
|
+
walk.cuts += 1;
|
|
556
|
+
return finishExposure([], false);
|
|
557
|
+
}
|
|
558
|
+
const cutsBefore = walk.cuts;
|
|
559
|
+
walk.trail.add(key);
|
|
560
|
+
const members = [];
|
|
561
|
+
let incomplete = false;
|
|
562
|
+
for (const member of current.own) {
|
|
563
|
+
if (mode === "module" && isPrivateName(member.declared) && member.def !== model.unhide)
|
|
564
|
+
continue;
|
|
565
|
+
members.push(applyLens(ownAsMember(member, mode === "import"), lens));
|
|
566
|
+
}
|
|
567
|
+
for (const resolved of current.loads) {
|
|
568
|
+
const { load, target } = resolved;
|
|
569
|
+
if (load.kind === "use" || load.nested)
|
|
570
|
+
continue;
|
|
571
|
+
if (target.type === "unresolved")
|
|
572
|
+
incomplete = true;
|
|
573
|
+
// `@forward "sass:color"` exposes built-in members this scan does not list. A miss is unproven.
|
|
574
|
+
if (load.kind === "forward" && target.type === "builtin")
|
|
575
|
+
incomplete = true;
|
|
576
|
+
if (target.type !== "file")
|
|
577
|
+
continue;
|
|
578
|
+
const forwarded = load.kind === "forward";
|
|
579
|
+
const inner = exposureOf(model, fileIdentityKey(target.file), forwarded ? "module" : "import", walk, lens);
|
|
580
|
+
if (inner.incomplete)
|
|
581
|
+
incomplete = true;
|
|
582
|
+
for (const member of inner.members) {
|
|
583
|
+
if (forwarded) {
|
|
584
|
+
if (passesFilter(member, load))
|
|
585
|
+
members.push(withForward(member, load));
|
|
586
|
+
}
|
|
587
|
+
else if (mode === "import") {
|
|
588
|
+
members.push(member);
|
|
589
|
+
}
|
|
590
|
+
else if (!isPrivateName(member.exposed) || member.def === model.unhide) {
|
|
591
|
+
// A consumer of a module cannot see a private name that `@import` brought in.
|
|
592
|
+
members.push({ ...member, importOnly: false });
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
walk.trail.delete(key);
|
|
597
|
+
const exposure = finishExposure(members, incomplete);
|
|
598
|
+
if (walk.cuts === cutsBefore)
|
|
599
|
+
cache.set(cacheKey, exposure);
|
|
600
|
+
return exposure;
|
|
601
|
+
}
|
|
602
|
+
function newWalk() {
|
|
603
|
+
return { trail: new Set(), cuts: 0 };
|
|
604
|
+
}
|
|
605
|
+
function kindMatches(member, use) {
|
|
606
|
+
if (use === "callable")
|
|
607
|
+
return member === "mixin" || member === "function";
|
|
608
|
+
return member === use;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Cheap test before a use is resolved: it can name the member only when the kind agrees and the
|
|
612
|
+
* spelling ends with the declared name, because a forward prefix only adds text in front.
|
|
613
|
+
*/
|
|
614
|
+
function scssUseMayNameMember(use, member) {
|
|
615
|
+
return kindMatches(member.kind, use.kind) && use.canonical.endsWith(member.declared);
|
|
616
|
+
}
|
|
617
|
+
function lookup(table, kind, canonical) {
|
|
618
|
+
if (kind === "callable") {
|
|
619
|
+
return [...(table.get(`mixin:${canonical}`) ?? []), ...(table.get(`function:${canonical}`) ?? [])];
|
|
620
|
+
}
|
|
621
|
+
return [...(table.get(`${kind}:${canonical}`) ?? [])];
|
|
622
|
+
}
|
|
623
|
+
/** One member per declaration. A declaration reached by several paths keeps the most permissive access. */
|
|
624
|
+
function uniqueMembers(members) {
|
|
625
|
+
const byDef = new Map();
|
|
626
|
+
for (const member of members) {
|
|
627
|
+
const seen = byDef.get(member.def);
|
|
628
|
+
if (!seen)
|
|
629
|
+
byDef.set(member.def, member);
|
|
630
|
+
else if (member.importOnly && !seen.importOnly)
|
|
631
|
+
byDef.set(member.def, { ...seen, importOnly: true });
|
|
632
|
+
}
|
|
633
|
+
return [...byDef.values()];
|
|
634
|
+
}
|
|
635
|
+
function choose(members, ambiguousReason, emptyReason, incomplete) {
|
|
636
|
+
const [first, ...rest] = uniqueMembers(members);
|
|
637
|
+
if (!first) {
|
|
638
|
+
if (!incomplete)
|
|
639
|
+
return { status: "not_found", reason: emptyReason };
|
|
640
|
+
return { status: "not_found", reason: `${emptyReason}; a loaded module did not resolve`, unproven: true };
|
|
641
|
+
}
|
|
642
|
+
if (rest.length)
|
|
643
|
+
return { status: "not_found", reason: ambiguousReason, unproven: true };
|
|
644
|
+
return {
|
|
645
|
+
status: "ok",
|
|
646
|
+
def: first.def,
|
|
647
|
+
prefix: first.prefix,
|
|
648
|
+
access: first.importOnly ? "import" : "module",
|
|
649
|
+
filters: first.filters,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function unresolvedIntroducesMembers(loads, beforeOffset) {
|
|
653
|
+
return loads.some((resolved) => {
|
|
654
|
+
if (beforeOffset !== undefined && resolved.load.at > beforeOffset)
|
|
655
|
+
return false;
|
|
656
|
+
if (resolved.target.type !== "unresolved")
|
|
657
|
+
return false;
|
|
658
|
+
return resolved.load.kind === "import" || (resolved.load.kind === "use" && resolved.load.star);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* The scoped mixin or function that owns a bare include or call, using the same nearest-owner
|
|
663
|
+
* rule as reference collection. Null when no same-kind callable covers the use.
|
|
664
|
+
*/
|
|
665
|
+
function scopedCallableOwner(current, use) {
|
|
666
|
+
if (use.namespace !== null || (use.kind !== "mixin" && use.kind !== "function"))
|
|
667
|
+
return undefined;
|
|
668
|
+
if (use.syntax !== "include" && use.syntax !== "call")
|
|
669
|
+
return undefined;
|
|
670
|
+
return nearestScopedCallable(current.facts, use.kind, use.canonical, use.nameStart);
|
|
671
|
+
}
|
|
672
|
+
/** True when a scoped mixin or function of the same kind and name covers this bare use. */
|
|
673
|
+
function coveringCallable(current, use) {
|
|
674
|
+
return scopedCallableOwner(current, use) !== undefined;
|
|
675
|
+
}
|
|
676
|
+
/** The indexed symbol for a scoped callable, matched by the name's end offset and Sass spelling. */
|
|
677
|
+
function indexedScopedCallable(current, local) {
|
|
678
|
+
for (const def of current.defAt.values()) {
|
|
679
|
+
if (def.kind !== SymbolKind.Function || def.range.end.index !== local.from)
|
|
680
|
+
continue;
|
|
681
|
+
if (sassCanonical(def.localName) !== local.canonical)
|
|
682
|
+
continue;
|
|
683
|
+
return def;
|
|
684
|
+
}
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* An indexed scoped mixin or function is the target of the bare include or call it owns.
|
|
689
|
+
* An unindexed one stays a shadow, so module lookup cannot claim the name.
|
|
690
|
+
*/
|
|
691
|
+
function resolveCoveringCallable(current, use) {
|
|
692
|
+
const owner = scopedCallableOwner(current, use);
|
|
693
|
+
if (!owner)
|
|
694
|
+
return null;
|
|
695
|
+
const def = indexedScopedCallable(current, owner);
|
|
696
|
+
if (!def)
|
|
697
|
+
return { status: "shadow", indexed: false };
|
|
698
|
+
return { status: "ok", def, prefix: "", access: "own", filters: [] };
|
|
699
|
+
}
|
|
700
|
+
function coveringLocals(current, use) {
|
|
701
|
+
if (use.kind !== "variable")
|
|
702
|
+
return [];
|
|
703
|
+
const bindings = current.localsByName.get(use.canonical) ?? [];
|
|
704
|
+
return bindings.filter((local) => use.nameStart >= local.from && use.nameStart < local.to);
|
|
705
|
+
}
|
|
706
|
+
function resolveLoadName(model, current, use) {
|
|
707
|
+
const resolved = use.loadIndex === null ? undefined : current.loads[use.loadIndex];
|
|
708
|
+
if (!resolved)
|
|
709
|
+
return { status: "not_found", reason: "SCSS module load is missing" };
|
|
710
|
+
const { load, target } = resolved;
|
|
711
|
+
if (target.type === "builtin")
|
|
712
|
+
return { status: "not_found", reason: "Built-in Sass module" };
|
|
713
|
+
if (target.type === "unresolved") {
|
|
714
|
+
return { status: "not_found", reason: `Module URL ${load.url} did not resolve`, unproven: true };
|
|
715
|
+
}
|
|
716
|
+
const isConfig = use.syntax === "config";
|
|
717
|
+
// `with` can configure a private variable of the loaded module, which every other use through
|
|
718
|
+
// `@use` is denied, so only a configuration key looks at the declarations the module hides.
|
|
719
|
+
const inner = exposureOf(model, fileIdentityKey(target.file), isConfig ? "import" : "module", newWalk());
|
|
720
|
+
const written = sassCanonical(use.text);
|
|
721
|
+
// `show` and `hide` spell the names this forward exposes; `with` spells the names the target exposes.
|
|
722
|
+
const loadPrefix = isConfig ? "" : sassCanonical(load.prefix);
|
|
723
|
+
const matches = inner.members.filter((member) => {
|
|
724
|
+
if (!kindMatches(member.kind, use.kind) || loadPrefix + member.exposed !== written)
|
|
725
|
+
return false;
|
|
726
|
+
return !isConfig || member.isDefault;
|
|
727
|
+
});
|
|
728
|
+
const choice = choose(matches, `More than one SCSS member matches ${use.text}`, `No SCSS member matches ${use.text}`, inner.incomplete);
|
|
729
|
+
if (choice.status !== "ok")
|
|
730
|
+
return choice;
|
|
731
|
+
return { ...choice, prefix: isConfig ? choice.prefix : load.prefix + choice.prefix };
|
|
732
|
+
}
|
|
733
|
+
function matchingNamespaces(file, namespace) {
|
|
734
|
+
// Sass compares namespaces as written: `my-lib` and `my_lib` are different namespaces.
|
|
735
|
+
return file.loads.filter((resolved) => resolved.load.kind === "use" && resolved.load.namespace === namespace);
|
|
736
|
+
}
|
|
737
|
+
/** One load per target, so two importers that load the same module do not look like a clash. */
|
|
738
|
+
function uniqueTargets(loads) {
|
|
739
|
+
const seen = new Set();
|
|
740
|
+
const unique = [];
|
|
741
|
+
for (const resolved of loads) {
|
|
742
|
+
const { target, load } = resolved;
|
|
743
|
+
const key = target.type === "file" ? `file:${fileIdentityKey(target.file)}` : `${target.type}:${load.url}`;
|
|
744
|
+
if (seen.has(key))
|
|
745
|
+
continue;
|
|
746
|
+
seen.add(key);
|
|
747
|
+
unique.push(resolved);
|
|
748
|
+
}
|
|
749
|
+
return unique;
|
|
750
|
+
}
|
|
751
|
+
/** An @imported partial inherits the caller's @use binding, not the spelling of its namespace. */
|
|
752
|
+
function inheritsMetaLookup(model, current, use) {
|
|
753
|
+
if (!use.dynamic)
|
|
754
|
+
return false;
|
|
755
|
+
const importers = importerChain(model, current.key);
|
|
756
|
+
if (use.namespace === null) {
|
|
757
|
+
if (current.ownByName.has(use.kind + ":" + use.canonical))
|
|
758
|
+
return false;
|
|
759
|
+
return importers.some((importer) => importer.loads.some(({ load, target }) => load.kind === "use" && load.star && load.url.trim() === "sass:meta" && target.type === "builtin"));
|
|
760
|
+
}
|
|
761
|
+
const namespace = use.namespace;
|
|
762
|
+
if (matchingNamespaces(current, namespace).length)
|
|
763
|
+
return false;
|
|
764
|
+
const inherited = uniqueTargets(importers.flatMap((importer) => matchingNamespaces(importer, namespace)));
|
|
765
|
+
return inherited.some(({ load, target }) => load.url.trim() === "sass:meta" && target.type === "builtin");
|
|
766
|
+
}
|
|
767
|
+
/** True when `key` forwards `sass:meta`, directly or through another `@forward`. */
|
|
768
|
+
function moduleForwardsSassMeta(model, key, seen) {
|
|
769
|
+
if (seen.has(key))
|
|
770
|
+
return false;
|
|
771
|
+
seen.add(key);
|
|
772
|
+
const current = model.files.get(key);
|
|
773
|
+
if (!current)
|
|
774
|
+
return false;
|
|
775
|
+
for (const resolved of current.loads) {
|
|
776
|
+
if (resolved.load.kind !== "forward")
|
|
777
|
+
continue;
|
|
778
|
+
if (resolved.target.type === "builtin") {
|
|
779
|
+
if (resolved.load.url.trim() === "sass:meta")
|
|
780
|
+
return true;
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (resolved.target.type === "file" && moduleForwardsSassMeta(model, fileIdentityKey(resolved.target.file), seen)) {
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* True when `namespace` is a `@use` whose module forwards `sass:meta`. The file's own `@use` wins.
|
|
791
|
+
* A partial with no such `@use` sees the namespace of a file that `@import`s it, same as other lookups.
|
|
792
|
+
*/
|
|
793
|
+
function namespaceForwardsSassMeta(model, current, namespace) {
|
|
794
|
+
let namespaces = matchingNamespaces(current, namespace);
|
|
795
|
+
if (!namespaces.length) {
|
|
796
|
+
namespaces = uniqueTargets(importerChain(model, current.key).flatMap((importer) => matchingNamespaces(importer, namespace)));
|
|
797
|
+
}
|
|
798
|
+
for (const resolved of namespaces) {
|
|
799
|
+
if (resolved.target.type === "builtin" && resolved.load.url.trim() === "sass:meta")
|
|
800
|
+
return true;
|
|
801
|
+
if (resolved.target.type === "file" &&
|
|
802
|
+
moduleForwardsSassMeta(model, fileIdentityKey(resolved.target.file), new Set())) {
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
function resolveNamespaced(model, current, use, namespace) {
|
|
809
|
+
let namespaces = matchingNamespaces(current, namespace);
|
|
810
|
+
if (!namespaces.length) {
|
|
811
|
+
// A file that an `@import` runs inside its importer can use the namespaces of that importer.
|
|
812
|
+
const inherited = importerChain(model, current.key).flatMap((importer) => matchingNamespaces(importer, namespace));
|
|
813
|
+
namespaces = uniqueTargets(inherited);
|
|
814
|
+
}
|
|
815
|
+
if (namespaces.length > 1) {
|
|
816
|
+
return { status: "not_found", reason: `SCSS namespace ${namespace} is declared more than once`, unproven: true };
|
|
817
|
+
}
|
|
818
|
+
const found = namespaces[0];
|
|
819
|
+
if (!found)
|
|
820
|
+
return { status: "not_found", reason: `No SCSS namespace ${namespace}` };
|
|
821
|
+
if (found.target.type === "builtin")
|
|
822
|
+
return { status: "not_found", reason: "Built-in Sass module" };
|
|
823
|
+
if (found.target.type === "unresolved") {
|
|
824
|
+
return { status: "not_found", reason: `Module URL ${found.load.url} did not resolve`, unproven: true };
|
|
825
|
+
}
|
|
826
|
+
const inner = exposureOf(model, fileIdentityKey(found.target.file), "module", newWalk());
|
|
827
|
+
return choose(lookup(inner.byName, use.kind, use.canonical), `More than one SCSS member matches ${namespace}.${use.text}`, `No SCSS member matches ${namespace}.${use.text}`, inner.incomplete);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Adds the members of the `@use ... as *` and `@import` loads of `file` that have `kind` and the
|
|
831
|
+
* name `canonical`. A nested `@import` is left out, see {@link nestedMembers}. Returns true when a
|
|
832
|
+
* load could not be read, so the lookup is not complete.
|
|
833
|
+
*/
|
|
834
|
+
function collectLoaded(model, file, kind, canonical, into, beforeOffset) {
|
|
835
|
+
let incomplete = false;
|
|
836
|
+
for (const resolved of file.loads) {
|
|
837
|
+
if (beforeOffset !== undefined && resolved.load.at > beforeOffset)
|
|
838
|
+
continue;
|
|
839
|
+
if (resolved.target.type !== "file" || resolved.load.nested)
|
|
840
|
+
continue;
|
|
841
|
+
const star = resolved.load.kind === "use" && resolved.load.star;
|
|
842
|
+
const legacy = resolved.load.kind === "import";
|
|
843
|
+
if (!star && !legacy)
|
|
844
|
+
continue;
|
|
845
|
+
const inner = exposureOf(model, fileIdentityKey(resolved.target.file), legacy ? "import" : "module", newWalk());
|
|
846
|
+
if (inner.incomplete)
|
|
847
|
+
incomplete = true;
|
|
848
|
+
into.push(...lookup(inner.byName, kind, canonical));
|
|
849
|
+
}
|
|
850
|
+
return incomplete;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* The members that the `@import`s nested in a rule of `file` give a name. Sass declares what a
|
|
854
|
+
* nested `@import` brings in inside the rule that holds it, and this scan does not follow blocks,
|
|
855
|
+
* so a use of such a name is unproven instead of resolved to either side.
|
|
856
|
+
*/
|
|
857
|
+
function nestedMembers(model, file, kind, canonical) {
|
|
858
|
+
const found = [];
|
|
859
|
+
for (const resolved of file.loads) {
|
|
860
|
+
if (!resolved.load.nested || resolved.target.type !== "file")
|
|
861
|
+
continue;
|
|
862
|
+
const inner = exposureOf(model, fileIdentityKey(resolved.target.file), "import", newWalk());
|
|
863
|
+
found.push(...lookup(inner.byName, kind, canonical));
|
|
864
|
+
}
|
|
865
|
+
return found;
|
|
866
|
+
}
|
|
867
|
+
function nestedImportUnproven(use) {
|
|
868
|
+
return { status: "not_found", reason: `A nested @import may declare ${use.text} in its own block`, unproven: true };
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* True when `use` is a bare `get-function` call that precedes the user-defined function in this
|
|
872
|
+
* file. dart-sass still calls the legacy built-in for that earlier call.
|
|
873
|
+
*/
|
|
874
|
+
function legacyGetFunctionBeforeDeclaration(current, use, member) {
|
|
875
|
+
if (use.syntax !== "call" || use.namespace !== null)
|
|
876
|
+
return false;
|
|
877
|
+
if (use.canonical !== "get-function" && use.canonical !== "call")
|
|
878
|
+
return false;
|
|
879
|
+
if (member.kind !== "function")
|
|
880
|
+
return false;
|
|
881
|
+
const declaredAt = scssOffsetAt(current.facts, member.def.range.start.line, member.def.range.start.column);
|
|
882
|
+
return declaredAt > use.nameStart;
|
|
883
|
+
}
|
|
884
|
+
function legacyBuiltinUse(dynamic) {
|
|
885
|
+
const at = dynamic.at ?? 0;
|
|
886
|
+
return {
|
|
887
|
+
kind: "function",
|
|
888
|
+
syntax: "call",
|
|
889
|
+
namespace: null,
|
|
890
|
+
text: dynamic.form,
|
|
891
|
+
canonical: dynamic.form,
|
|
892
|
+
nameStart: at,
|
|
893
|
+
nameEnd: at + dynamic.form.length,
|
|
894
|
+
hitStart: at,
|
|
895
|
+
loadIndex: null,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* True when a dynamic lookup in `file` is not proved to be a user-defined function. A bare legacy
|
|
900
|
+
* built-in (`get-function` or an existence predicate) is proved when it resolves to a function
|
|
901
|
+
* another file declares.
|
|
902
|
+
*/
|
|
903
|
+
async function hasUnprovenDynamic(index, file) {
|
|
904
|
+
if (file.inheritedDynamics.length)
|
|
905
|
+
return true;
|
|
906
|
+
for (const dynamic of file.facts.dynamics) {
|
|
907
|
+
if (!dynamic.legacyBare)
|
|
908
|
+
return true;
|
|
909
|
+
const resolved = await resolveScssUse(index, file.file, legacyBuiltinUse(dynamic));
|
|
910
|
+
if (resolved.status === "ok" && fileIdentityKey(resolved.def.file) !== file.key)
|
|
911
|
+
continue;
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
function resolveUnnamespaced(model, current, use) {
|
|
917
|
+
const covered = resolveCoveringCallable(current, use);
|
|
918
|
+
if (covered)
|
|
919
|
+
return covered;
|
|
920
|
+
const covering = coveringLocals(current, use);
|
|
921
|
+
if (covering.length) {
|
|
922
|
+
const nearest = covering[covering.length - 1];
|
|
923
|
+
// The generic scope walker compares names exactly. Let it answer only when it can prove this
|
|
924
|
+
// binding, not a same-spelled outer module member behind an equivalent -/_ local.
|
|
925
|
+
const indexed = nearest.spelling === use.text &&
|
|
926
|
+
current.defAt.has(defKey(nearest.range.start.line, nearest.range.start.column, nearest.canonical));
|
|
927
|
+
return { status: "shadow", indexed };
|
|
928
|
+
}
|
|
929
|
+
if (use.kind === "variable" && current.unprovenFlowAssigns.has(use.canonical)) {
|
|
930
|
+
return {
|
|
931
|
+
status: "not_found",
|
|
932
|
+
reason: "A flow-control assignment may change an imported variable",
|
|
933
|
+
unproven: true,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
if (nestedMembers(model, current, use.kind, use.canonical).length)
|
|
937
|
+
return nestedImportUnproven(use);
|
|
938
|
+
const ownKey = `${use.kind}:${use.canonical}`;
|
|
939
|
+
const own = current.ownByName.get(ownKey) ?? [];
|
|
940
|
+
if (own.length > 1) {
|
|
941
|
+
return { status: "not_found", reason: `More than one SCSS member matches ${use.text}`, unproven: true };
|
|
942
|
+
}
|
|
943
|
+
const [only] = own;
|
|
944
|
+
const legacyEarly = !!only && legacyGetFunctionBeforeDeclaration(current, use, only);
|
|
945
|
+
const declaredLater = !!only && memberDeclaredAfter(current, use, only);
|
|
946
|
+
// A mixin or function body runs when it is invoked. A declaration later in the file may or may
|
|
947
|
+
// not exist yet, so the call is not a proven reference of either side.
|
|
948
|
+
if (only && declaredLater && use.deferred && !legacyEarly) {
|
|
949
|
+
return { status: "not_found", reason: `A deferred call may or may not see ${use.text}`, unproven: true };
|
|
950
|
+
}
|
|
951
|
+
// An eager call before the declaration is plain CSS unless a preceding load already supplies the
|
|
952
|
+
// name. A later `get-function` never owns an earlier call: that call is the legacy built-in.
|
|
953
|
+
const ignoreOwn = legacyEarly || (declaredLater && !use.deferred);
|
|
954
|
+
if (only && !ignoreOwn) {
|
|
955
|
+
return { status: "ok", def: only.def, prefix: "", access: "own", filters: [] };
|
|
956
|
+
}
|
|
957
|
+
const before = ignoreOwn ? use.nameStart : undefined;
|
|
958
|
+
if (unresolvedIntroducesMembers(current.loads, before)) {
|
|
959
|
+
return { status: "not_found", reason: "An unresolved SCSS import may also define this name", unproven: true };
|
|
960
|
+
}
|
|
961
|
+
const found = [];
|
|
962
|
+
let incomplete = collectLoaded(model, current, use.kind, use.canonical, found, before);
|
|
963
|
+
if (!found.length) {
|
|
964
|
+
// `@import` runs a file inside the scope of its importer, so the file reads what the importer
|
|
965
|
+
// declared and loaded. The order of the imports is not modeled.
|
|
966
|
+
for (const importer of importerChain(model, current.key)) {
|
|
967
|
+
if (nestedMembers(model, importer, use.kind, use.canonical).length)
|
|
968
|
+
return nestedImportUnproven(use);
|
|
969
|
+
for (const member of importer.ownByName.get(ownKey) ?? [])
|
|
970
|
+
found.push(ownAsMember(member, true));
|
|
971
|
+
if (unresolvedIntroducesMembers(importer.loads))
|
|
972
|
+
incomplete = true;
|
|
973
|
+
if (collectLoaded(model, importer, use.kind, use.canonical, found))
|
|
974
|
+
incomplete = true;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return choose(found, `More than one SCSS member matches ${use.text}`, `No SCSS member matches ${use.text}`, incomplete);
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* `@extend` reaches the placeholders of its module and of the modules it loads. `@import` runs a
|
|
981
|
+
* file inside its importer, so the file, the files that import it, and everything they `@import`
|
|
982
|
+
* are one module, where a private name stays visible. Modules loaded with `@use` or `@forward`
|
|
983
|
+
* show public names only.
|
|
984
|
+
*/
|
|
985
|
+
function visiblePlaceholders(model, current, canonical) {
|
|
986
|
+
const sameModule = new Map();
|
|
987
|
+
for (const file of [current, ...importerChain(model, current.key)])
|
|
988
|
+
sameModule.set(file.key, file);
|
|
989
|
+
let unproven = false;
|
|
990
|
+
const importQueue = [...sameModule.values()];
|
|
991
|
+
for (let next = importQueue.shift(); next; next = importQueue.shift()) {
|
|
992
|
+
for (const resolved of next.loads) {
|
|
993
|
+
if (resolved.load.kind !== "import" || resolved.target.type !== "file")
|
|
994
|
+
continue;
|
|
995
|
+
const key = fileIdentityKey(resolved.target.file);
|
|
996
|
+
if (sameModule.has(key))
|
|
997
|
+
continue;
|
|
998
|
+
const loaded = model.files.get(key);
|
|
999
|
+
if (!loaded) {
|
|
1000
|
+
unproven = true;
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
sameModule.set(key, loaded);
|
|
1004
|
+
importQueue.push(loaded);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const found = new Set();
|
|
1008
|
+
const collect = (file, publicOnly) => {
|
|
1009
|
+
for (const entry of file.placeholders) {
|
|
1010
|
+
if (entry.declared === canonical && !(publicOnly && isPrivateName(entry.declared)))
|
|
1011
|
+
found.add(entry.def);
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
for (const file of sameModule.values())
|
|
1015
|
+
collect(file, false);
|
|
1016
|
+
const seen = new Set(sameModule.keys());
|
|
1017
|
+
const queue = [...sameModule.values()];
|
|
1018
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
1019
|
+
for (const resolved of next.loads) {
|
|
1020
|
+
if (resolved.target.type === "unresolved")
|
|
1021
|
+
unproven = true;
|
|
1022
|
+
if (resolved.target.type !== "file")
|
|
1023
|
+
continue;
|
|
1024
|
+
const key = fileIdentityKey(resolved.target.file);
|
|
1025
|
+
if (seen.has(key))
|
|
1026
|
+
continue;
|
|
1027
|
+
seen.add(key);
|
|
1028
|
+
const loaded = model.files.get(key);
|
|
1029
|
+
if (!loaded) {
|
|
1030
|
+
unproven = true;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
collect(loaded, true);
|
|
1034
|
+
queue.push(loaded);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
return { found, unproven, sameModule };
|
|
1038
|
+
}
|
|
1039
|
+
function resolvePlaceholder(model, current, use) {
|
|
1040
|
+
const { found, unproven, sameModule } = visiblePlaceholders(model, current, use.canonical);
|
|
1041
|
+
if (unproven) {
|
|
1042
|
+
return { status: "not_found", reason: "SCSS @extend modules were not all readable", unproven: true };
|
|
1043
|
+
}
|
|
1044
|
+
const [first, ...rest] = [...found];
|
|
1045
|
+
if (!first)
|
|
1046
|
+
return { status: "not_found", reason: `No SCSS placeholder matches %${use.text}` };
|
|
1047
|
+
if (rest.length) {
|
|
1048
|
+
return {
|
|
1049
|
+
status: "not_found",
|
|
1050
|
+
reason: `More than one SCSS placeholder matches %${use.text}`,
|
|
1051
|
+
unproven: true,
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
const declaringKey = fileIdentityKey(first.file);
|
|
1055
|
+
let access = "module";
|
|
1056
|
+
if (declaringKey === current.key)
|
|
1057
|
+
access = "own";
|
|
1058
|
+
else if (sameModule.has(declaringKey))
|
|
1059
|
+
access = "import";
|
|
1060
|
+
return { status: "ok", def: first, prefix: "", access, filters: [] };
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Resolves one scanned use. `shadow` means an enclosing local owns the name, so module
|
|
1064
|
+
* lookup must not claim it. `unproven` means more than one member matches, or a load that
|
|
1065
|
+
* could contribute members did not resolve.
|
|
1066
|
+
*/
|
|
1067
|
+
export async function resolveScssUse(index, file, use) {
|
|
1068
|
+
const model = await projectModel(index);
|
|
1069
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1070
|
+
if (!current)
|
|
1071
|
+
return { status: "not_found", reason: "SCSS file is not indexed", unproven: true };
|
|
1072
|
+
if (use.kind === "placeholder")
|
|
1073
|
+
return resolvePlaceholder(model, current, use);
|
|
1074
|
+
if (use.loadIndex !== null)
|
|
1075
|
+
return resolveLoadName(model, current, use);
|
|
1076
|
+
if (use.dynamic && current.inheritedDynamics.includes(use.dynamic)) {
|
|
1077
|
+
return { status: "not_found", reason: "An inherited sass:meta lookup is dynamic", unproven: true };
|
|
1078
|
+
}
|
|
1079
|
+
if (use.namespace !== null)
|
|
1080
|
+
return resolveNamespaced(model, current, use, use.namespace);
|
|
1081
|
+
return resolveUnnamespaced(model, current, use);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* The member a definition declares: a top-level member, or a placeholder at any depth. Null for
|
|
1085
|
+
* locals, parameters, and other selectors.
|
|
1086
|
+
*/
|
|
1087
|
+
export async function scssMemberForDefinition(index, def) {
|
|
1088
|
+
const model = await projectModel(index);
|
|
1089
|
+
const current = model.files.get(fileIdentityKey(def.file));
|
|
1090
|
+
if (!current)
|
|
1091
|
+
return null;
|
|
1092
|
+
const at = defKey(def.range.start.line, def.range.start.column, sassCanonical(def.localName));
|
|
1093
|
+
if (!current.defAt.has(at))
|
|
1094
|
+
return null;
|
|
1095
|
+
const member = current.own.find((entry) => entry.siteKeys.has(at));
|
|
1096
|
+
if (member)
|
|
1097
|
+
return member;
|
|
1098
|
+
const placeholder = current.placeholders.find((entry) => entry.siteKeys.has(at));
|
|
1099
|
+
if (placeholder) {
|
|
1100
|
+
return { kind: "placeholder", declared: placeholder.declared, def: placeholder.def, sites: placeholder.sites };
|
|
1101
|
+
}
|
|
1102
|
+
const joined = current.joinedAssignments.find((entry) => entry.range.start.line === def.range.start.line && entry.range.start.column === def.range.start.column);
|
|
1103
|
+
return joined?.member ?? null;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* True when an indexed block variable is spelled with a different mix of `-` and `_` than a use it covers.
|
|
1107
|
+
* The exact-name reference walk omits that use, so coverage must not claim to be complete.
|
|
1108
|
+
*/
|
|
1109
|
+
export function scssLocalSpellingGap(facts, def) {
|
|
1110
|
+
if (!def.localName.startsWith("$"))
|
|
1111
|
+
return false;
|
|
1112
|
+
const local = facts.locals.find((entry) => entry.range.start.line === def.range.start.line && entry.range.start.column === def.range.start.column);
|
|
1113
|
+
if (!local)
|
|
1114
|
+
return false;
|
|
1115
|
+
return facts.uses.some((use) => use.kind === "variable" &&
|
|
1116
|
+
use.namespace === null &&
|
|
1117
|
+
use.canonical === local.canonical &&
|
|
1118
|
+
use.text !== local.spelling &&
|
|
1119
|
+
use.nameStart >= local.from &&
|
|
1120
|
+
use.nameStart < local.to);
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* True when a scanned local nearer than `def` covers `at` and spells the same Sass variable with a
|
|
1124
|
+
* different mix of `-` and `_`. The exact-name walk would keep that read on `def`.
|
|
1125
|
+
*/
|
|
1126
|
+
export function scssNearerEquivalentLocal(facts, def, at) {
|
|
1127
|
+
if (!def.localName.startsWith("$"))
|
|
1128
|
+
return false;
|
|
1129
|
+
const offset = at.index ?? scssOffsetAt(facts, at.line, at.column);
|
|
1130
|
+
if (offset < 0)
|
|
1131
|
+
return false;
|
|
1132
|
+
const canonical = sassCanonical(def.localName);
|
|
1133
|
+
const own = facts.locals.find((entry) => entry.range.start.line === def.range.start.line && entry.range.start.column === def.range.start.column);
|
|
1134
|
+
if (!own)
|
|
1135
|
+
return false;
|
|
1136
|
+
let nearest;
|
|
1137
|
+
for (const local of facts.locals) {
|
|
1138
|
+
if (local.canonical !== canonical || offset < local.from || offset >= local.to)
|
|
1139
|
+
continue;
|
|
1140
|
+
if (!nearest || local.from >= nearest.from)
|
|
1141
|
+
nearest = local;
|
|
1142
|
+
}
|
|
1143
|
+
return nearest !== undefined && nearest !== own && nearest.spelling !== own.spelling;
|
|
1144
|
+
}
|
|
1145
|
+
/** The imported variable a joined flow-control assignment names, when the click is on that assignment. */
|
|
1146
|
+
export async function scssAssignmentDefinition(index, file, line, column) {
|
|
1147
|
+
const model = await projectModel(index);
|
|
1148
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1149
|
+
if (!current)
|
|
1150
|
+
return null;
|
|
1151
|
+
const offset = scssOffsetAt(current.facts, line, column);
|
|
1152
|
+
if (offset < 0)
|
|
1153
|
+
return null;
|
|
1154
|
+
const joined = current.joinedAssignments.find((entry) => {
|
|
1155
|
+
const start = entry.range.start.index;
|
|
1156
|
+
const end = entry.range.end.index;
|
|
1157
|
+
return start !== undefined && end !== undefined && offset >= start && offset < end;
|
|
1158
|
+
});
|
|
1159
|
+
return joined?.member.def ?? null;
|
|
1160
|
+
}
|
|
1161
|
+
/** True when this indexed variable is a file-scope flow assignment that could not be joined. */
|
|
1162
|
+
export async function scssFlowLocalUnproven(index, def) {
|
|
1163
|
+
if (!def.localName.startsWith("$"))
|
|
1164
|
+
return false;
|
|
1165
|
+
const model = await projectModel(index);
|
|
1166
|
+
const current = model.files.get(fileIdentityKey(def.file));
|
|
1167
|
+
return current?.unprovenFlowAssigns.has(sassCanonical(def.localName)) ?? false;
|
|
1168
|
+
}
|
|
1169
|
+
function existenceNamesLocal(dynamic, canonical, local) {
|
|
1170
|
+
if (dynamic.form !== "variable-exists")
|
|
1171
|
+
return false;
|
|
1172
|
+
if (dynamic.arg !== null && sassCanonical(dynamic.arg) !== canonical)
|
|
1173
|
+
return false;
|
|
1174
|
+
const at = dynamic.at;
|
|
1175
|
+
if (local && at !== undefined && (at < local.from || at >= local.to))
|
|
1176
|
+
return false;
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* True when a block local, parameter, or loop variable may be the variable `variable-exists`
|
|
1181
|
+
* names. `global-variable-exists` does not see a block local. A proved user function is not the
|
|
1182
|
+
* built-in. Module members are left to {@link hasUnprovenDynamic}.
|
|
1183
|
+
*/
|
|
1184
|
+
export async function scssBlockLocalExistenceUnproven(index, def) {
|
|
1185
|
+
if (!def.localName.startsWith("$"))
|
|
1186
|
+
return false;
|
|
1187
|
+
if (await scssMemberForDefinition(index, def))
|
|
1188
|
+
return false;
|
|
1189
|
+
const model = await projectModel(index);
|
|
1190
|
+
const current = model.files.get(fileIdentityKey(def.file));
|
|
1191
|
+
if (!current)
|
|
1192
|
+
return false;
|
|
1193
|
+
const canonical = sassCanonical(def.localName);
|
|
1194
|
+
const local = current.facts.locals.find((entry) => entry.range.start.line === def.range.start.line && entry.range.start.column === def.range.start.column);
|
|
1195
|
+
for (const dynamic of current.facts.dynamics) {
|
|
1196
|
+
if (!existenceNamesLocal(dynamic, canonical, local))
|
|
1197
|
+
continue;
|
|
1198
|
+
if (dynamic.legacyBare) {
|
|
1199
|
+
const resolved = await resolveScssUse(index, current.file, legacyBuiltinUse(dynamic));
|
|
1200
|
+
if (resolved.status === "ok" && fileIdentityKey(resolved.def.file) !== current.key)
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
for (const use of current.facts.uses) {
|
|
1206
|
+
if (!use.dynamic || !current.inheritedDynamics.includes(use.dynamic))
|
|
1207
|
+
continue;
|
|
1208
|
+
if (!existenceNamesLocal({ ...use.dynamic, at: use.nameStart }, canonical, local))
|
|
1209
|
+
continue;
|
|
1210
|
+
return true;
|
|
1211
|
+
}
|
|
1212
|
+
return false;
|
|
1213
|
+
}
|
|
1214
|
+
/** Reference span for a use. Null when a forward prefix cannot be split from the member. */
|
|
1215
|
+
export function scssUseEditRange(facts, use, prefix) {
|
|
1216
|
+
const span = memberEditSpan(use.text, prefix);
|
|
1217
|
+
if (!span)
|
|
1218
|
+
return null;
|
|
1219
|
+
return scssOffsetRange(facts, use.nameStart + span.start, use.nameStart + span.end);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* True when `file` assigns the variable `canonical` and also loads a module that declares it with
|
|
1223
|
+
* `@use ... as *`. Sass then assigns the variable of the module, so one variable has declarations
|
|
1224
|
+
* in two files, and this scan does not join them. A namespaced `@use` keeps the variables apart.
|
|
1225
|
+
*/
|
|
1226
|
+
function assignsStarVariable(model, file, canonical) {
|
|
1227
|
+
if (!file.ownByName.has(`variable:${canonical}`))
|
|
1228
|
+
return false;
|
|
1229
|
+
return file.loads.some((resolved) => {
|
|
1230
|
+
if (resolved.target.type !== "file" || resolved.load.kind !== "use" || !resolved.load.star)
|
|
1231
|
+
return false;
|
|
1232
|
+
const inner = exposureOf(model, fileIdentityKey(resolved.target.file), "module", newWalk());
|
|
1233
|
+
return lookup(inner.byName, "variable", canonical).length > 0;
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
/** True when a consumer of `file` can reach `def` through it: the file declares it, forwards it, or imports it. */
|
|
1237
|
+
function exposesDefinition(model, file, def) {
|
|
1238
|
+
return ["module", "import"].some((mode) => exposureOf(model, file.key, mode, newWalk()).members.some((member) => member.def === def));
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Files that can see `member`, plus files whose unresolved URL or `meta.load-css` may load the
|
|
1242
|
+
* declaring file. `unavailable` files were not fully proved. A directory index (`_index.scss`)
|
|
1243
|
+
* is also reached through the name of its directory.
|
|
1244
|
+
*/
|
|
1245
|
+
export async function scssReferenceCandidates(index, member) {
|
|
1246
|
+
const def = member.def;
|
|
1247
|
+
const model = await projectModel(index);
|
|
1248
|
+
const files = [];
|
|
1249
|
+
const seen = new Set();
|
|
1250
|
+
const queue = [def.file];
|
|
1251
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
1252
|
+
const key = fileIdentityKey(next);
|
|
1253
|
+
if (seen.has(key))
|
|
1254
|
+
continue;
|
|
1255
|
+
seen.add(key);
|
|
1256
|
+
files.push(next);
|
|
1257
|
+
queue.push(...(model.reverse.get(key) ?? []));
|
|
1258
|
+
queue.push(...(model.hostsByTarget.get(key) ?? []));
|
|
1259
|
+
}
|
|
1260
|
+
// `@import` runs a file inside the scope of its importer, so a file that an included file
|
|
1261
|
+
// imports can use the member without loading the file that declares it. A target that is not
|
|
1262
|
+
// SCSS (plain CSS) cannot read a Sass member.
|
|
1263
|
+
const importQueue = [...files];
|
|
1264
|
+
for (let next = importQueue.shift(); next; next = importQueue.shift()) {
|
|
1265
|
+
for (const resolved of model.files.get(fileIdentityKey(next))?.loads ?? []) {
|
|
1266
|
+
if (resolved.load.kind !== "import" || resolved.target.type !== "file")
|
|
1267
|
+
continue;
|
|
1268
|
+
const target = resolved.target.file;
|
|
1269
|
+
const key = fileIdentityKey(target);
|
|
1270
|
+
if (seen.has(key) || !isScssFile(index, target))
|
|
1271
|
+
continue;
|
|
1272
|
+
seen.add(key);
|
|
1273
|
+
files.push(target);
|
|
1274
|
+
importQueue.push(target);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
const unavailable = [];
|
|
1278
|
+
const unavailableKeys = new Set();
|
|
1279
|
+
const markUnavailable = (file) => {
|
|
1280
|
+
const key = fileIdentityKey(file);
|
|
1281
|
+
if (unavailableKeys.has(key))
|
|
1282
|
+
return;
|
|
1283
|
+
unavailableKeys.add(key);
|
|
1284
|
+
unavailable.push(file);
|
|
1285
|
+
};
|
|
1286
|
+
// `@import` puts files in one global scope. A file there that declares the same name assigns the
|
|
1287
|
+
// same variable (or replaces the same mixin or function), and this scan does not follow which
|
|
1288
|
+
// declaration a use reads, so that file is not proved.
|
|
1289
|
+
if (member.kind !== "placeholder") {
|
|
1290
|
+
const declaringKey = fileIdentityKey(def.file);
|
|
1291
|
+
const sharedScope = new Set([declaringKey]);
|
|
1292
|
+
const importerQueue = [declaringKey];
|
|
1293
|
+
for (let next = importerQueue.shift(); next; next = importerQueue.shift()) {
|
|
1294
|
+
for (const importerKey of model.importers.get(next) ?? []) {
|
|
1295
|
+
if (sharedScope.has(importerKey))
|
|
1296
|
+
continue;
|
|
1297
|
+
sharedScope.add(importerKey);
|
|
1298
|
+
importerQueue.push(importerKey);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
const childQueue = [...sharedScope];
|
|
1302
|
+
for (let next = childQueue.shift(); next; next = childQueue.shift()) {
|
|
1303
|
+
for (const resolved of model.files.get(next)?.loads ?? []) {
|
|
1304
|
+
if (resolved.load.kind !== "import" || resolved.target.type !== "file")
|
|
1305
|
+
continue;
|
|
1306
|
+
const key = fileIdentityKey(resolved.target.file);
|
|
1307
|
+
if (sharedScope.has(key))
|
|
1308
|
+
continue;
|
|
1309
|
+
sharedScope.add(key);
|
|
1310
|
+
childQueue.push(key);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const ownKey = `${member.kind}:${member.declared}`;
|
|
1314
|
+
for (const key of sharedScope) {
|
|
1315
|
+
const other = model.files.get(key);
|
|
1316
|
+
if (key !== declaringKey && other?.ownByName.has(ownKey))
|
|
1317
|
+
markUnavailable(other.file);
|
|
1318
|
+
if (member.kind === "variable" && other?.unprovenFlowAssigns.has(member.declared))
|
|
1319
|
+
markUnavailable(other.file);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
// A URL can reach the member through the declaring file, or through a module that forwards or
|
|
1323
|
+
// imports it. Each of them gives a stem that a load of an unresolved URL can name.
|
|
1324
|
+
const stems = new Set();
|
|
1325
|
+
const addStems = (file) => {
|
|
1326
|
+
const stem = moduleStem(file);
|
|
1327
|
+
stems.add(stem);
|
|
1328
|
+
if (stem === "index")
|
|
1329
|
+
stems.add(moduleStem(path.dirname(file)));
|
|
1330
|
+
};
|
|
1331
|
+
addStems(def.file);
|
|
1332
|
+
for (const file of files) {
|
|
1333
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1334
|
+
if (current && exposesDefinition(model, current, def))
|
|
1335
|
+
addStems(file);
|
|
1336
|
+
}
|
|
1337
|
+
for (const current of model.files.values()) {
|
|
1338
|
+
if (seen.has(current.key))
|
|
1339
|
+
continue;
|
|
1340
|
+
const unresolvedMatch = [...stems].some((stem) => current.unresolvedStems.has(stem));
|
|
1341
|
+
const dynamicMatch = current.dynamicStems.has(null) || [...stems].some((stem) => current.dynamicStems.has(stem));
|
|
1342
|
+
if (!unresolvedMatch && !dynamicMatch)
|
|
1343
|
+
continue;
|
|
1344
|
+
seen.add(current.key);
|
|
1345
|
+
files.push(current.file);
|
|
1346
|
+
markUnavailable(current.file);
|
|
1347
|
+
}
|
|
1348
|
+
for (const stem of stems) {
|
|
1349
|
+
for (const host of model.hostsByStem.get(stem) ?? []) {
|
|
1350
|
+
if (seen.has(fileIdentityKey(host)))
|
|
1351
|
+
continue;
|
|
1352
|
+
seen.add(fileIdentityKey(host));
|
|
1353
|
+
files.push(host);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
for (const file of files) {
|
|
1357
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1358
|
+
// A host file keeps its styles in blocks this scan does not read, so it is never proved.
|
|
1359
|
+
if (!current) {
|
|
1360
|
+
markUnavailable(file);
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
// A load that did not resolve may reach members this scan cannot see.
|
|
1364
|
+
const unresolvedLoad = current.loads.some((resolved) => resolved.target.type === "unresolved");
|
|
1365
|
+
if (unresolvedLoad || (await hasUnprovenDynamic(index, current)))
|
|
1366
|
+
markUnavailable(file);
|
|
1367
|
+
if (member.kind === "variable" && assignsStarVariable(model, current, member.declared))
|
|
1368
|
+
markUnavailable(file);
|
|
1369
|
+
}
|
|
1370
|
+
return { files, unavailable };
|
|
1371
|
+
}
|
|
1372
|
+
export function scssUseAt(facts, line, column) {
|
|
1373
|
+
const offset = scssOffsetAt(facts, line, column);
|
|
1374
|
+
if (offset < 0)
|
|
1375
|
+
return undefined;
|
|
1376
|
+
return facts.uses.find((use) => offset >= use.hitStart && offset < use.nameEnd);
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Members one file sees without a namespace: its own declarations, `@use ... as *`, `@import`, and
|
|
1380
|
+
* the scope it inherits from the files that `@import` it.
|
|
1381
|
+
*/
|
|
1382
|
+
function globalScope(model, current, lens) {
|
|
1383
|
+
const members = [];
|
|
1384
|
+
for (const file of [current, ...importerChain(model, current.key)]) {
|
|
1385
|
+
for (const own of file.own)
|
|
1386
|
+
members.push(applyLens(ownAsMember(own, true), lens));
|
|
1387
|
+
for (const resolved of file.loads) {
|
|
1388
|
+
if (resolved.target.type !== "file")
|
|
1389
|
+
continue;
|
|
1390
|
+
const star = resolved.load.kind === "use" && resolved.load.star;
|
|
1391
|
+
const legacy = resolved.load.kind === "import";
|
|
1392
|
+
if (!star && !legacy)
|
|
1393
|
+
continue;
|
|
1394
|
+
const inner = exposureOf(model, fileIdentityKey(resolved.target.file), legacy ? "import" : "module", newWalk(), lens);
|
|
1395
|
+
members.push(...inner.members);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return members;
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Same-kind members that would share the new name with `def` in a scope that sees `def`: the
|
|
1402
|
+
* module that declares it, every module that exposes it, and every file that sees it without a
|
|
1403
|
+
* namespace (`@use ... as *`, `@import`). A forward prefix stays in front of the new name. A
|
|
1404
|
+
* placeholder has no namespace, so no file that sees it may also see another placeholder that has
|
|
1405
|
+
* the new name.
|
|
1406
|
+
*/
|
|
1407
|
+
export async function scssRenameCollisions(index, def, newName) {
|
|
1408
|
+
const model = await projectModel(index);
|
|
1409
|
+
const member = await scssMemberForDefinition(index, def);
|
|
1410
|
+
if (!member)
|
|
1411
|
+
return [];
|
|
1412
|
+
// Sass compares a placeholder as written and folds `_` into `-` for every other member.
|
|
1413
|
+
const next = member.kind === "placeholder" ? sassBare(newName) : sassCanonical(newName);
|
|
1414
|
+
if (next === member.declared)
|
|
1415
|
+
return [];
|
|
1416
|
+
const collisions = new Set();
|
|
1417
|
+
const { files } = await scssReferenceCandidates(index, member);
|
|
1418
|
+
if (member.kind === "placeholder") {
|
|
1419
|
+
// An `@extend` reaches the placeholders of its own scope, so each file that sees the renamed
|
|
1420
|
+
// placeholder is a scope where the new name must be free.
|
|
1421
|
+
for (const file of files) {
|
|
1422
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1423
|
+
if (!current)
|
|
1424
|
+
continue;
|
|
1425
|
+
for (const found of visiblePlaceholders(model, current, next).found) {
|
|
1426
|
+
if (found !== member.def)
|
|
1427
|
+
collisions.add(found);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return [...collisions];
|
|
1431
|
+
}
|
|
1432
|
+
const check = (members) => {
|
|
1433
|
+
// One declaration can be exposed under several spellings (a module that forwards it with two
|
|
1434
|
+
// prefixes), and each spelling takes the new name in its own place.
|
|
1435
|
+
for (const exposure of members) {
|
|
1436
|
+
if (exposure.def !== member.def)
|
|
1437
|
+
continue;
|
|
1438
|
+
const wanted = exposure.lensFrom !== undefined
|
|
1439
|
+
? exposure.exposed
|
|
1440
|
+
: exposure.exposed.slice(0, exposure.exposed.length - member.declared.length) + next;
|
|
1441
|
+
for (const entry of members) {
|
|
1442
|
+
if (entry.kind === member.kind && entry.def !== member.def && entry.exposed === wanted) {
|
|
1443
|
+
collisions.add(entry.def);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
// A private member is hidden from the files that load it with `@use ... as *` or `@forward`. A
|
|
1449
|
+
// public member joins their scopes, so those scopes must not already use the new name.
|
|
1450
|
+
const becomesPublic = isPrivateName(member.declared) && !isPrivateName(next);
|
|
1451
|
+
const view = becomesPublic ? { ...model, exposures: new Map(), unhide: member.def } : model;
|
|
1452
|
+
// Recompute `show` and `hide` under the new name. An entry the rename edits keeps exposing the
|
|
1453
|
+
// member; an entry that already spells the new name can newly expose it or hide it.
|
|
1454
|
+
const lens = { def: member.def, from: member.declared, to: next, cache: new Map() };
|
|
1455
|
+
for (const file of files) {
|
|
1456
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1457
|
+
if (!current)
|
|
1458
|
+
continue;
|
|
1459
|
+
check(exposureOf(view, current.key, "module", newWalk(), lens).members);
|
|
1460
|
+
check(exposureOf(view, current.key, "import", newWalk(), lens).members);
|
|
1461
|
+
check(globalScope(view, current, lens));
|
|
1462
|
+
}
|
|
1463
|
+
return [...collisions];
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* The uses in `facts` that resolve to `member`, in source order. `unproven` is true when a use that
|
|
1467
|
+
* may name the member could not be resolved, so the file is not fully proved.
|
|
1468
|
+
*/
|
|
1469
|
+
export async function scssMemberUses(index, file, facts, member) {
|
|
1470
|
+
const matches = [];
|
|
1471
|
+
let unproven = false;
|
|
1472
|
+
for (const use of facts.uses) {
|
|
1473
|
+
if (!scssUseMayNameMember(use, member))
|
|
1474
|
+
continue;
|
|
1475
|
+
const resolved = await resolveScssUse(index, file, use);
|
|
1476
|
+
if (resolved.status === "shadow")
|
|
1477
|
+
continue;
|
|
1478
|
+
if (resolved.status === "not_found") {
|
|
1479
|
+
if (resolved.unproven)
|
|
1480
|
+
unproven = true;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
if (sameDef(resolved.def, member.def))
|
|
1484
|
+
matches.push({ use, resolved });
|
|
1485
|
+
}
|
|
1486
|
+
return { matches, unproven };
|
|
1487
|
+
}
|
|
1488
|
+
/** `with`, `show`, and `hide` name a member without reading it, so its privacy does not matter to them. */
|
|
1489
|
+
function readsMember(use) {
|
|
1490
|
+
return use.syntax !== "config" && use.syntax !== "show" && use.syntax !== "hide";
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* The scoped mixin or function that owns a bare use at `at`.
|
|
1494
|
+
* Scope runs from the end of the name to the end of the containing block. The nearest same-kind
|
|
1495
|
+
* callable with that Sass name (`-` and `_` equivalent) wins. `renamed` treats one local as if it
|
|
1496
|
+
* already had a different name, which is how a rename captures calls.
|
|
1497
|
+
*/
|
|
1498
|
+
function nearestScopedCallable(facts, kind, canonical, at, renamed) {
|
|
1499
|
+
let best;
|
|
1500
|
+
for (const candidate of facts.callableLocals) {
|
|
1501
|
+
if (candidate.kind !== kind)
|
|
1502
|
+
continue;
|
|
1503
|
+
const name = renamed && candidate === renamed.local ? renamed.canonical : candidate.canonical;
|
|
1504
|
+
if (name !== canonical || at < candidate.from || at >= candidate.to)
|
|
1505
|
+
continue;
|
|
1506
|
+
if (!best || candidate.from >= best.from)
|
|
1507
|
+
best = candidate;
|
|
1508
|
+
}
|
|
1509
|
+
return best;
|
|
1510
|
+
}
|
|
1511
|
+
function scopedCallableLocal(facts, def) {
|
|
1512
|
+
const end = def.range.end.index;
|
|
1513
|
+
if (end === undefined || def.kind !== SymbolKind.Function)
|
|
1514
|
+
return undefined;
|
|
1515
|
+
const canonical = sassCanonical(def.localName);
|
|
1516
|
+
return facts.callableLocals.find((candidate) => candidate.from === end && candidate.canonical === canonical);
|
|
1517
|
+
}
|
|
1518
|
+
const SCOPED_FUNCTION_LOOKUPS = new Set(["function-exists", "get-function", "call"]);
|
|
1519
|
+
const SCOPED_MIXIN_LOOKUPS = new Set(["mixin-exists", "get-mixin"]);
|
|
1520
|
+
/**
|
|
1521
|
+
* True when this lookup may name a scoped callable. A non-literal or interpolated argument may
|
|
1522
|
+
* be any name. `-` and `_` are the same character. `call` and `get-function` name functions;
|
|
1523
|
+
* `get-mixin` and `mixin-exists` name mixins.
|
|
1524
|
+
*/
|
|
1525
|
+
function dynamicMayNameCallable(dynamic, local) {
|
|
1526
|
+
const forms = local.kind === "function" ? SCOPED_FUNCTION_LOOKUPS : SCOPED_MIXIN_LOOKUPS;
|
|
1527
|
+
if (!forms.has(dynamic.form))
|
|
1528
|
+
return false;
|
|
1529
|
+
if (dynamic.arg === null || dynamic.arg.includes("#{"))
|
|
1530
|
+
return true;
|
|
1531
|
+
return sassCanonical(dynamic.arg) === local.canonical;
|
|
1532
|
+
}
|
|
1533
|
+
/** The lookup sits in the span where `local` is the nearest visible callable of its name. */
|
|
1534
|
+
function lookupInCallableScope(facts, local, at) {
|
|
1535
|
+
if (at === undefined || at < local.from || at >= local.to)
|
|
1536
|
+
return false;
|
|
1537
|
+
return nearestScopedCallable(facts, local.kind, local.canonical, at) === local;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* A legacy bare built-in is not a lookup when a user function already owns the call: a scoped
|
|
1541
|
+
* function of that name, or a function proved in another file. A later declaration does not.
|
|
1542
|
+
*/
|
|
1543
|
+
async function legacyBuiltinOwnedByUser(index, current, dynamic) {
|
|
1544
|
+
if (!dynamic.legacyBare)
|
|
1545
|
+
return false;
|
|
1546
|
+
const resolved = await resolveScssUse(index, current.file, legacyBuiltinUse(dynamic));
|
|
1547
|
+
if (resolved.status === "shadow")
|
|
1548
|
+
return true;
|
|
1549
|
+
if (resolved.status !== "ok")
|
|
1550
|
+
return false;
|
|
1551
|
+
if (fileIdentityKey(resolved.def.file) !== current.key)
|
|
1552
|
+
return true;
|
|
1553
|
+
// A same-file scoped function owns the bare name; a top-level function in this file does not
|
|
1554
|
+
// reach here, because a declaration above the call is not recorded as a legacy built-in.
|
|
1555
|
+
return scopedCallableLocal(current.facts, resolved.def) !== undefined;
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* True when a dynamic lookup in the span where `def` is visible may name that scoped mixin or
|
|
1559
|
+
* function. Existence predicates, `call`, and `get-function` name a function; `mixin-exists` and
|
|
1560
|
+
* `get-mixin` name a mixin. The call is a proved `sass:meta` binding or a legacy bare built-in.
|
|
1561
|
+
* A lookup outside the block, or one a user function owns, does not. Static uses stay references.
|
|
1562
|
+
*/
|
|
1563
|
+
export async function scssScopedCallableDynamicUnproven(index, def) {
|
|
1564
|
+
if (!isScssFile(index, def.file) || def.kind !== SymbolKind.Function)
|
|
1565
|
+
return false;
|
|
1566
|
+
const model = await projectModel(index);
|
|
1567
|
+
const current = model.files.get(fileIdentityKey(def.file));
|
|
1568
|
+
if (!current)
|
|
1569
|
+
return false;
|
|
1570
|
+
const local = scopedCallableLocal(current.facts, def);
|
|
1571
|
+
if (!local)
|
|
1572
|
+
return false;
|
|
1573
|
+
for (const dynamic of current.facts.dynamics) {
|
|
1574
|
+
if (!dynamicMayNameCallable(dynamic, local) || !lookupInCallableScope(current.facts, local, dynamic.at)) {
|
|
1575
|
+
continue;
|
|
1576
|
+
}
|
|
1577
|
+
if (await legacyBuiltinOwnedByUser(index, current, dynamic))
|
|
1578
|
+
continue;
|
|
1579
|
+
return true;
|
|
1580
|
+
}
|
|
1581
|
+
for (const use of current.facts.uses) {
|
|
1582
|
+
if (!use.dynamic || !current.inheritedDynamics.includes(use.dynamic))
|
|
1583
|
+
continue;
|
|
1584
|
+
// A bare call a scoped user function owns is that function, not the built-in.
|
|
1585
|
+
if (coveringCallable(current, use))
|
|
1586
|
+
continue;
|
|
1587
|
+
const dynamic = use.dynamic;
|
|
1588
|
+
if (!dynamicMayNameCallable(dynamic, local))
|
|
1589
|
+
continue;
|
|
1590
|
+
if (!lookupInCallableScope(current.facts, local, dynamic.at ?? use.nameStart))
|
|
1591
|
+
continue;
|
|
1592
|
+
return true;
|
|
1593
|
+
}
|
|
1594
|
+
return false;
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Bare same-kind calls and includes a scoped mixin or function owns.
|
|
1598
|
+
* Null when `def` is not a scoped callable. `unproven` when the file cannot be scanned, so
|
|
1599
|
+
* coverage must not claim to be complete. A dynamic lookup that may name the callable is reported
|
|
1600
|
+
* separately by {@link scssScopedCallableDynamicUnproven}; these ranges stay the proved uses.
|
|
1601
|
+
*/
|
|
1602
|
+
export async function scssScopedCallableReferences(index, def) {
|
|
1603
|
+
if (!isScssFile(index, def.file) || def.kind !== SymbolKind.Function)
|
|
1604
|
+
return null;
|
|
1605
|
+
const facts = await scssFactsForFile(index, def.file);
|
|
1606
|
+
if (!facts || def.range.end.index === undefined)
|
|
1607
|
+
return { ranges: [], unproven: true };
|
|
1608
|
+
const local = scopedCallableLocal(facts, def);
|
|
1609
|
+
if (!local)
|
|
1610
|
+
return null;
|
|
1611
|
+
const syntax = local.kind === "mixin" ? "include" : "call";
|
|
1612
|
+
const ranges = [];
|
|
1613
|
+
for (const use of facts.uses) {
|
|
1614
|
+
if (use.namespace !== null || use.kind !== local.kind || use.syntax !== syntax)
|
|
1615
|
+
continue;
|
|
1616
|
+
if (nearestScopedCallable(facts, local.kind, use.canonical, use.nameStart) !== local)
|
|
1617
|
+
continue;
|
|
1618
|
+
ranges.push(scssOffsetRange(facts, use.nameStart, use.nameEnd));
|
|
1619
|
+
}
|
|
1620
|
+
return { ranges, unproven: false };
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* A scoped callable is not a module member. `captured` lists bare calls already spelled with
|
|
1624
|
+
* the proposed name that this local would own, and `shadowed` lists calls this local owns when
|
|
1625
|
+
* a different same-kind scoped callable would own the edited name (`-` and `_` equivalent).
|
|
1626
|
+
* `unaccounted` lists calls of the original name that this local owns and the reference walk
|
|
1627
|
+
* omitted; a nearer scoped callable keeps its own calls. A bare dynamic callee (`get-function`,
|
|
1628
|
+
* `call`, `get-mixin`, `load-css`, the other meta lookups, or a `_` spelling) whose name is the
|
|
1629
|
+
* proposed name is captured too: the rename would make that call invoke this callable. A namespaced
|
|
1630
|
+
* dynamic call is not. Null means `def` is not a scoped callable.
|
|
1631
|
+
*/
|
|
1632
|
+
export async function scssScopedCallableRenameSites(index, def, newName, references) {
|
|
1633
|
+
const model = await projectModel(index);
|
|
1634
|
+
const current = model.files.get(fileIdentityKey(def.file));
|
|
1635
|
+
if (!current)
|
|
1636
|
+
return null;
|
|
1637
|
+
const local = scopedCallableLocal(current.facts, def);
|
|
1638
|
+
if (!local)
|
|
1639
|
+
return null;
|
|
1640
|
+
const next = sassCanonical(newName);
|
|
1641
|
+
const syntax = local.kind === "mixin" ? "include" : "call";
|
|
1642
|
+
const covered = new Set();
|
|
1643
|
+
for (const reference of references) {
|
|
1644
|
+
if (fileIdentityKey(reference.file) === current.key && reference.range.start.index !== undefined) {
|
|
1645
|
+
covered.add(reference.range.start.index);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
const captured = [];
|
|
1649
|
+
const shadowed = [];
|
|
1650
|
+
const unaccounted = [];
|
|
1651
|
+
const renamed = { local, canonical: next };
|
|
1652
|
+
for (const use of current.facts.uses) {
|
|
1653
|
+
if (use.namespace !== null || use.kind !== local.kind || use.syntax !== syntax)
|
|
1654
|
+
continue;
|
|
1655
|
+
const range = scssOffsetRange(current.facts, use.nameStart, use.nameEnd);
|
|
1656
|
+
const ownedByLocal = use.canonical === local.canonical &&
|
|
1657
|
+
nearestScopedCallable(current.facts, local.kind, local.canonical, use.nameStart) === local;
|
|
1658
|
+
if (next !== local.canonical &&
|
|
1659
|
+
use.canonical === next &&
|
|
1660
|
+
nearestScopedCallable(current.facts, local.kind, next, use.nameStart, renamed) === local) {
|
|
1661
|
+
captured.push({ file: current.file, range });
|
|
1662
|
+
}
|
|
1663
|
+
// The edited reference keeps this local's place. A nearer same-kind callable of the new
|
|
1664
|
+
// name, including an equivalent `-`/`_` spelling, would own the call instead.
|
|
1665
|
+
if (ownedByLocal && next !== local.canonical) {
|
|
1666
|
+
const owner = nearestScopedCallable(current.facts, local.kind, next, use.nameStart, renamed);
|
|
1667
|
+
if (owner && owner !== local)
|
|
1668
|
+
shadowed.push({ file: current.file, range });
|
|
1669
|
+
}
|
|
1670
|
+
if (ownedByLocal && !covered.has(use.nameStart)) {
|
|
1671
|
+
unaccounted.push({ file: current.file, range, text: use.text });
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
// A `sass:meta as *` lookup is only a dynamic, not a use, so the loop above misses it. The
|
|
1675
|
+
// proposed name is the callee (`get-function`, `load-css`, ...), folded so `_` matches `-`.
|
|
1676
|
+
const nextForm = dynamicFormOf(newName);
|
|
1677
|
+
if (nextForm && next !== local.canonical) {
|
|
1678
|
+
const parsed = await ensureParsedContext(current.file, index.parsed?.get(fileIdentityKey(current.file)), index.languageExtensions);
|
|
1679
|
+
const source = parsed.source;
|
|
1680
|
+
const seen = new Set(captured.map((site) => site.range.start.index));
|
|
1681
|
+
for (const dynamic of current.facts.dynamics) {
|
|
1682
|
+
if (dynamic.form !== nextForm || dynamic.at === undefined)
|
|
1683
|
+
continue;
|
|
1684
|
+
const at = dynamic.at;
|
|
1685
|
+
if (at < local.from || at >= local.to)
|
|
1686
|
+
continue;
|
|
1687
|
+
if (source.charCodeAt(at - 1) === 0x2e)
|
|
1688
|
+
continue;
|
|
1689
|
+
if (nearestScopedCallable(current.facts, local.kind, next, at, renamed) !== local)
|
|
1690
|
+
continue;
|
|
1691
|
+
if (seen.has(at))
|
|
1692
|
+
continue;
|
|
1693
|
+
seen.add(at);
|
|
1694
|
+
captured.push({ file: current.file, range: scssOffsetRange(current.facts, at, at + nextForm.length) });
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return { captured, shadowed, unaccounted };
|
|
1698
|
+
}
|
|
1699
|
+
/** True when a file that sees `def` forwards it through a `show` or `hide` list. */
|
|
1700
|
+
function isForwardedThroughList(model, files, def) {
|
|
1701
|
+
return files.some((file) => {
|
|
1702
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1703
|
+
if (!current?.loads.some(({ load }) => load.kind === "forward" && (load.show || load.hide)))
|
|
1704
|
+
return false;
|
|
1705
|
+
return exposureOf(model, current.key, "module", newWalk()).members.some((entry) => entry.def === def && entry.filters.length > 0);
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* True when a `hide` list on the path of a use names the member under its new name. A `show` list
|
|
1710
|
+
* and a `hide` list that name the member today are edited with it, so only another entry can hide
|
|
1711
|
+
* the new name.
|
|
1712
|
+
*/
|
|
1713
|
+
function hidesRenamedMember(filters, member, next) {
|
|
1714
|
+
return filters.some(({ load, exposed }) => {
|
|
1715
|
+
const renamed = exposed.slice(0, exposed.length - member.declared.length) + next;
|
|
1716
|
+
return (load.hide ?? []).some((entry) => (entry.kind === "variable") === (member.kind === "variable") && sassCanonical(entry.name) === renamed);
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* The uses that the new name would break.
|
|
1721
|
+
* - A block variable captures a bare variable use, or a scoped mixin/function captures a bare
|
|
1722
|
+
* include/call, when its same-kind name matches the forward prefix plus the proposed name.
|
|
1723
|
+
* A namespaced use cannot be captured.
|
|
1724
|
+
* - A name that starts with `-` or `_` is private to its module, so a use through `@use` or
|
|
1725
|
+
* `@forward` could no longer reach it. `@import` and the declaring file keep access.
|
|
1726
|
+
* - A `@forward` with a `hide` list that names the new name (with the forward prefix) hides the
|
|
1727
|
+
* member from the use that reads it through that forward.
|
|
1728
|
+
*/
|
|
1729
|
+
export async function scssRenameUseConflicts(index, def, newName) {
|
|
1730
|
+
const captured = [];
|
|
1731
|
+
const inaccessible = [];
|
|
1732
|
+
const hidden = [];
|
|
1733
|
+
const member = await scssMemberForDefinition(index, def);
|
|
1734
|
+
if (!member)
|
|
1735
|
+
return { captured, inaccessible, hidden };
|
|
1736
|
+
const next = sassCanonical(newName);
|
|
1737
|
+
if (next === member.declared)
|
|
1738
|
+
return { captured, inaccessible, hidden };
|
|
1739
|
+
const becomesPrivate = isPrivateName(next) && !isPrivateName(member.declared);
|
|
1740
|
+
const canBeCaptured = member.kind !== "placeholder";
|
|
1741
|
+
// A placeholder is in no `show` or `hide` list, so only a private name can break its uses.
|
|
1742
|
+
if (member.kind === "placeholder" && !becomesPrivate)
|
|
1743
|
+
return { captured, inaccessible, hidden };
|
|
1744
|
+
const model = await projectModel(index);
|
|
1745
|
+
const { files } = await scssReferenceCandidates(index, member);
|
|
1746
|
+
const mayBeHidden = isForwardedThroughList(model, files, member.def);
|
|
1747
|
+
if (!canBeCaptured && !becomesPrivate && !mayBeHidden)
|
|
1748
|
+
return { captured, inaccessible, hidden };
|
|
1749
|
+
for (const file of files) {
|
|
1750
|
+
const current = model.files.get(fileIdentityKey(file));
|
|
1751
|
+
if (!current)
|
|
1752
|
+
continue;
|
|
1753
|
+
const { matches } = await scssMemberUses(index, file, current.facts, member);
|
|
1754
|
+
for (const { use, resolved } of matches) {
|
|
1755
|
+
const range = scssUseEditRange(current.facts, use, resolved.prefix);
|
|
1756
|
+
if (!range)
|
|
1757
|
+
continue;
|
|
1758
|
+
if (canBeCaptured && use.namespace === null) {
|
|
1759
|
+
const spelled = sassCanonical(resolved.prefix) + next;
|
|
1760
|
+
if (member.kind === "variable" && use.syntax === "variable") {
|
|
1761
|
+
const bindings = current.localsByName.get(spelled);
|
|
1762
|
+
if (bindings?.some((binding) => use.nameStart >= binding.from && use.nameStart < binding.to)) {
|
|
1763
|
+
captured.push({ file, range });
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
else if ((use.syntax === "call" || use.syntax === "include") &&
|
|
1767
|
+
current.facts.callableLocals.some((local) => local.kind === member.kind &&
|
|
1768
|
+
local.canonical === spelled &&
|
|
1769
|
+
use.nameStart >= local.from &&
|
|
1770
|
+
use.nameStart < local.to)) {
|
|
1771
|
+
captured.push({ file, range });
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if (becomesPrivate && resolved.access === "module" && readsMember(use))
|
|
1775
|
+
inaccessible.push({ file, range });
|
|
1776
|
+
if (mayBeHidden && readsMember(use) && hidesRenamedMember(resolved.filters, member, next)) {
|
|
1777
|
+
hidden.push({ file, range });
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
return { captured, inaccessible, hidden };
|
|
1782
|
+
}
|
|
1783
|
+
/** Replacement text for one SCSS member edit, or null when the site is not that member. */
|
|
1784
|
+
export function scssReplacementText(localName, oldText, newName) {
|
|
1785
|
+
const newBare = sassBare(newName);
|
|
1786
|
+
if (sassCanonical(oldText) === sassCanonical(localName)) {
|
|
1787
|
+
if (localName.startsWith("$") && oldText.startsWith("$"))
|
|
1788
|
+
return newName.startsWith("$") ? newName : `$${newBare}`;
|
|
1789
|
+
if (localName.startsWith("%") && oldText.startsWith("%"))
|
|
1790
|
+
return `%${newBare}`;
|
|
1791
|
+
return newBare;
|
|
1792
|
+
}
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* The name an SCSS edit writes for a rename request. The symbol keeps its sigil, so a request of
|
|
1797
|
+
* `y` for `$x` writes `$y`, and the conflict checks must look for that spelling.
|
|
1798
|
+
*/
|
|
1799
|
+
export function scssRenamedName(localName, newName) {
|
|
1800
|
+
return scssReplacementText(localName, localName, newName) ?? newName;
|
|
1801
|
+
}
|
|
1802
|
+
//# sourceMappingURL=resolution.js.map
|