Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

228 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# check-lsp-originality.sh — Provenance guard for the hybrid LSP implementations.
#
# WHY THIS EXISTS
# The LSP layer (internal/cbm/lsp/) is an original C implementation whose
# behavior is "structurally inspired by and compatible with" the reference
# language servers (pyright, gopls, tsserver, Roslyn, clangd, rust-analyzer,
# …). We claim it contains NO source copied from those projects. This script
# is the documented, runnable check that backs that claim — run it before
# committing any NEW LSP implementation work, and review its report.
#
# WHAT IT CATCHES (the realistic copy "tells", and they survive a language port)
# 1. Verbatim string / error-message literals shared with a reference.
# 2. Verbatim comment phrases shared with a reference (the classic port tell:
# people re-type the logic but keep the explanatory comments).
# 3. Same-language structural clones via jscpd (uses `jscpd` on PATH, else
# `npx --yes jscpd`) — token-level clone detection, run only for clangd
# (C++ ↔ our C), the one reference close enough to C to tokenize alike.
#
# WHAT IT CANNOT CATCH (be honest — these need a human)
# An algorithm ported line-by-line with every identifier/comment rewritten is
# NOT machine-detectable cheaply across C↔TS/Go/C#/Rust/Java. A clean report
# is necessary, not sufficient: it means "no verbatim overlap found", not
# "provably independent". The git history (incremental, test-driven authorship)
# is the complementary evidence; keep it clean.
#
# USAGE
# bash scripts/check-lsp-originality.sh [--lang NAME] [--refresh]
# [--list-candidates] [--help]
# --lang NAME scan against ONE reference only (py|ts|go|cs|c|java|kotlin|rust|php|perl)
# --refresh re-fetch reference sources even if cached
# --list-candidates print the extracted local tokens and exit (no fetch; self-test)
# Exit 0 = no verbatim overlap found. Exit 1 = overlap(s) to review by a human.
#
# Reference sources are shallow/sparse-cloned into $LSP_REFS_DIR (default
# .lsp-refs/, gitignored). Adding a new LSP language? Add its upstream repo to
# the REFS manifest below so this guard covers it too.
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
TAG="[lsp-orig]"
LSP_DIR="$ROOT/internal/cbm/lsp"
REFS_DIR="${LSP_REFS_DIR:-$ROOT/.lsp-refs}"
# Tunables (override via env).
MIN_STR="${LSP_ORIG_MIN_STR:-16}" # min length of a string literal to consider
MIN_COMMENT="${LSP_ORIG_MIN_COMMENT:-30}" # min length of a comment phrase to consider
MIN_TOKENS="${LSP_ORIG_MIN_TOKENS:-50}" # jscpd structural-clone min token run
# Reference manifest: lang | git URL | sparse subpath (light checkout)
REFS=(
"py|https://github.com/microsoft/pyright|packages/pyright-internal/src"
"ts|https://github.com/microsoft/TypeScript|src/compiler,src/services"
"go|https://github.com/golang/tools|gopls/internal"
"cs|https://github.com/dotnet/roslyn|src/Compilers/CSharp/Portable"
"c|https://github.com/llvm/llvm-project|clang-tools-extra/clangd"
"java|https://github.com/eclipse-jdtls/eclipse.jdt.ls|org.eclipse.jdt.ls.core/src"
"kotlin|https://github.com/fwcd/kotlin-language-server|server/src"
"rust|https://github.com/rust-lang/rust-analyzer|crates"
# PHP was implemented from specs (PHP language ref + PSR-4), with no upstream
# LSP. We scan against phpactor (MIT) — the leading OSS PHP language server —
# purely for defensive copy-detection. NOT Intelephense (proprietary).
"php|https://github.com/phpactor/phpactor|lib"
# Perl (PR #461) was authored clean-room. We scan against PerlNavigator (MIT)
# — the leading OSS Perl language server — for defensive copy-detection.
"perl|https://github.com/bscan/PerlNavigator|server/src"
)
ONLY_LANG=""; REFRESH=0; LIST_ONLY=0
while [ $# -gt 0 ]; do
case "$1" in
--lang) ONLY_LANG="${2:-}"; shift 2 ;;
--refresh) REFRESH=1; shift ;;
--list-candidates) LIST_ONLY=1; shift ;;
--help|-h) sed -n '2,40p' "$0"; exit 0 ;;
*) echo "$TAG unknown arg: $1" >&2; exit 2 ;;
esac
done
if ! command -v rg >/dev/null 2>&1; then
echo "$TAG ERROR: ripgrep (rg) is required." >&2; exit 2
fi
# --- 1. Extract distinctive local tokens from our LSP C sources ---------------
# Skip generated/ (machine-emitted stdlib tables, not hand-authored logic).
# bash 3.2 (macOS default) has no `mapfile` — build the array with a read loop.
LSP_SRC=()
while IFS= read -r f; do LSP_SRC+=("$f"); done < <(
find "$LSP_DIR" -type f \( -name '*.c' -o -name '*.h' \) -not -path '*/generated/*' | sort)
if [ "${#LSP_SRC[@]}" -eq 0 ]; then
echo "$TAG ERROR: no LSP sources under $LSP_DIR" >&2; exit 2
fi
CAND="$(mktemp)"; trap 'rm -f "$CAND" "$CAND.s" "$CAND.c" 2>/dev/null' EXIT
# String literals of interest: >= MIN_STR chars, contain a letter and a space
# (favours human-readable phrases / messages over format specifiers & symbols).
rg --no-filename -o "\"[^\"]{${MIN_STR},}\"" "${LSP_SRC[@]}" 2>/dev/null \
| sed -E 's/^"//; s/"$//' \
| rg '[A-Za-z].* ' \
| rg -v '^(%|https?://|[0-9. ]+$)' \
| sort -u > "$CAND.s"
# Comment phrases: // line comments and single-line /* ... */, >= MIN_COMMENT.
{ rg --no-filename -o '//[^\n]+' "${LSP_SRC[@]}" 2>/dev/null | sed -E 's#^//+##'
rg --no-filename -o '/\*.*\*/' "${LSP_SRC[@]}" 2>/dev/null | sed -E 's#^/\*+##; s#\*+/$##'
} | sed -E 's/^[[:space:]*]+//; s/[[:space:]]+$//' \
| awk -v n="$MIN_COMMENT" 'length($0) >= n' \
| rg -v -i '^(todo|fixme|note|hack|xxx|copyright|spdx|clang-format|nolint|fallthrough)\b' \
| sort -u > "$CAND.c"
# Keep only "meaningful" tokens: at least two real words (drops decorative
# dividers like ===== / ----- / box-drawing rules that collide with banners).
cat "$CAND.s" "$CAND.c" | grep -v '^[[:space:]]*$' \
| rg -P '[A-Za-z]{3,}[^A-Za-z]+[A-Za-z]{2,}' \
| sort -u > "$CAND"
n_str=$(wc -l < "$CAND.s" | tr -d ' '); n_com=$(wc -l < "$CAND.c" | tr -d ' ')
echo "$TAG extracted $(wc -l < "$CAND" | tr -d ' ') candidate tokens from ${#LSP_SRC[@]} LSP sources ($n_str strings, $n_com comments)"
if [ "$LIST_ONLY" -eq 1 ]; then
echo "$TAG --- candidate tokens (no fetch performed) ---"
cat "$CAND"
exit 0
fi
if [ ! -s "$CAND" ]; then
echo "$TAG no candidate tokens — nothing to compare."; exit 0
fi
# --- 2. Fetch reference sources (shallow + sparse) ----------------------------
fetch_ref() {
local lang="$1" url="$2" subpath="$3" dst="$REFS_DIR/$lang"
if [ -d "$dst/.git" ] && [ "$REFRESH" -eq 0 ]; then return 0; fi
rm -rf "$dst"; mkdir -p "$dst"
echo "$TAG fetching $lang ($url :: $subpath) ..."
if ! git clone --depth 1 --filter=blob:none --sparse "$url" "$dst" >/dev/null 2>&1; then
echo "$TAG WARN: clone failed for $lang — skipping"; rm -rf "$dst"; return 1
fi
( cd "$dst" && git sparse-checkout set ${subpath//,/ } >/dev/null 2>&1 ) || true
}
# --- 3. Search candidates in each reference -----------------------------------
# Restrict to source code — license texts, changelogs and docs are not "copying".
REF_GLOBS=(-g '!**/LICENSE*' -g '!**/COPYING*' -g '!**/NOTICE*' -g '!**/CHANGELOG*'
-g '!**/*.md' -g '!**/*.txt' -g '!**/*.json' -g '!**/*.yml' -g '!**/*.yaml')
total_hits=0
for entry in "${REFS[@]}"; do
IFS='|' read -r lang url subpath <<< "$entry"
[ -n "$ONLY_LANG" ] && [ "$ONLY_LANG" != "$lang" ] && continue
fetch_ref "$lang" "$url" "$subpath" || continue
dst="$REFS_DIR/$lang"
[ -d "$dst" ] || continue
# Fixed-string search of every candidate across this reference tree.
hits=$(rg -F -l "${REF_GLOBS[@]}" -f "$CAND" "$dst" 2>/dev/null | wc -l | tr -d ' ')
if [ "${hits:-0}" -gt 0 ]; then
echo ""
echo "$TAG ⚠ overlap with reference '$lang' ($url):"
# Show which candidate matched where (cap output).
rg -F -n "${REF_GLOBS[@]}" -f "$CAND" "$dst" 2>/dev/null | head -40
total_hits=$((total_hits + hits))
else
echo "$TAG ok — no verbatim overlap with '$lang'"
fi
done
# --- 4. Structural-clone pass (clangd C++ <-> our C; the only same-ish language)
# String search misses ports that kept the code structure but rewrote identifiers
# and strings. jscpd is a token clone detector; it tokenizes per format, so we
# stage both trees as .cpp (C tokenizes fine as C++) and report only clone pairs
# that SPAN the two trees.
structural_hits=0
if [ -z "$ONLY_LANG" ] || [ "$ONLY_LANG" = "c" ]; then
JSCPD=""
if command -v jscpd >/dev/null 2>&1; then JSCPD="jscpd"
elif command -v npx >/dev/null 2>&1; then JSCPD="npx --yes jscpd"; fi
cdir="$REFS_DIR/c"
if [ -z "$JSCPD" ]; then
echo "$TAG structural pass skipped — install jscpd (npm i -g jscpd) or node/npx to enable"
elif [ ! -d "$cdir" ]; then
echo "$TAG structural pass skipped — clangd ref not fetched (run without --lang, or --lang c)"
else
stage="$(mktemp -d)"; jout="$(mktemp -d)"
for f in "${LSP_SRC[@]}"; do
cp "$f" "$stage/ours__$(echo "${f#$LSP_DIR/}" | tr '/' '_').cpp"
done
while IFS= read -r f; do
cp "$f" "$stage/clangd__$(echo "${f#$cdir/}" | tr '/' '_').cpp" 2>/dev/null
done < <(find "$cdir" -type f \( -name '*.cpp' -o -name '*.cc' -o -name '*.cxx' \
-o -name '*.h' -o -name '*.hpp' \))
echo "$TAG structural pass (jscpd, >= ${MIN_TOKENS} tokens, clangd C++ <-> our C) ..."
$JSCPD --silent --reporters json --output "$jout" --min-tokens "$MIN_TOKENS" "$stage" >/dev/null 2>&1 || true
pairs=""
if [ -f "$jout/jscpd-report.json" ]; then
pairs=$(jq -r '.duplicates[]?
| select((.firstFile.name|contains("ours__")) != (.secondFile.name|contains("ours__")))
| " \(.firstFile.name|gsub(".*/";"")) <-> \(.secondFile.name|gsub(".*/";"")) [\(.lines) lines]"' \
"$jout/jscpd-report.json" 2>/dev/null | sort -u)
fi
if [ -n "$pairs" ]; then
echo "$TAG ⚠ structural clones spanning our C and clangd:"; echo "$pairs" | head -20
structural_hits=1
else
echo "$TAG ok — no structural clones (>= ${MIN_TOKENS} tokens) between our C and clangd"
fi
rm -rf "$stage" "$jout"
fi
fi
echo ""
if [ "$total_hits" -gt 0 ] || [ "$structural_hits" -gt 0 ]; then
if [ "$total_hits" -gt 0 ]; then
echo "$TAG REVIEW NEEDED: $total_hits reference file(s) share a verbatim string/comment"
echo "$TAG with internal/cbm/lsp/. A hit is NOT proof of copying (common phrases collide)"
echo "$TAG — inspect each, confirm independent wording, reword genuinely-copied text."
fi
if [ "$structural_hits" -gt 0 ]; then
echo "$TAG REVIEW NEEDED: jscpd found structurally-cloned block(s) between our C and"
echo "$TAG clangd — inspect the pairs above and confirm independent implementation."
fi
echo "$TAG Re-run until clean before committing."
exit 1
fi
echo "$TAG CLEAN — no verbatim string/comment overlap with any reference, and no"
echo "$TAG structural clones (jscpd) between our C and clangd."
echo "$TAG (Reminder: this proves no verbatim/structural overlap, not provable"
echo "$TAG independence — the incremental git history is the complementary evidence.)"
exit 0