From 303bf0253ed9b37e958a467dd9ca54a4d6b6781c Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:13:14 -0700 Subject: [PATCH] impact: an export * is listed only where it re-exports the target's module `impact` on a method listed every `export * from` line in the repository as "re-exports the whole module it is declared in", whatever module the line pointed at. The reexport relation carried no source module, so the rule could only check that the barrel was a different file from the declaration. On a real React app, 14 of the 15 export-* rows for one component were unrelated barrels. On a real Express app, all 4 rows for one handler were. The barrel scan now resolves each export * specifier to a repository file: relative paths, the `./x.js` spelling of x.ts, directory index files, and the nearest tsconfig's baseUrl/paths. A package specifier resolves to nothing. The new relation reexport_from(f, l, src) is closed transitively (star_reach), and the row requires the declaring file to be reachable from that line. The exporter and the SQL port share one scanner, barrel_lines, so the two backends read a barrel the same way. Case typescript/export-star-scoped-to-its-source: direct and transitive export *, the .js spelling, a paths alias and a package export *. The control is the barrel's own declaration: the barrel's export * lines don't list it, and the export * that points at the barrel does. Named re-exports (typescript/barrel-reexport) are unchanged. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- .../skills/axiomcode/scripts/axiomcode-impact | 28 ++-- .../skills/axiomcode/scripts/dl/impact.dl | 8 +- .../skills/axiomcode/scripts/graph_sql.py | 145 +++++++++++++++--- .../case.json | 6 + .../src/aliased.ts | 1 + .../src/app.ts | 4 + .../src/feature.ts | 3 + .../src/index.ts | 2 + .../src/testing.ts | 1 + .../src/util/extra.ts | 1 + .../src/util/index.ts | 3 + .../src/util/numbers.ts | 1 + .../src/util/strings.ts | 1 + .../tsconfig.json | 7 + 14 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/case.json create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/aliased.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/app.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/feature.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/index.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/testing.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/util/extra.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/util/index.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/util/numbers.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/src/util/strings.ts create mode 100644 tests/cases/typescript/export-star-scoped-to-its-source/tsconfig.json diff --git a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact index ebf4d9da3..bbf662324 100755 --- a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact +++ b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact @@ -79,7 +79,7 @@ PER_QUERY = {'target', 'textuse', 'importuse', 'inside_target', 'nonsource', 'qu # `ref` is not here either: the alongside layer reads it to tell a sibling that touches the same field from one that does # not, for EVERY target kind. What is left is the layer only a field or type target can reach. _REF_LAYER = {'qualifier', 'typeref', 'typeref_file', 'literal', 'dec_literal', 'field', 'accessor', 'faccess', 'gen_table', - 'base_name', 'field_type', 'reexport', 'switch_over'} + 'base_name', 'field_type', 'reexport', 'reexport_from', 'switch_over'} _CONFIG = {'config', 'config_key_known', 'config_site'} # `reexport` is NOT out of a method's reach: rules 396 and 398 both start at target(q,"method",m,_) — the barrel # that names it, and the `export *` that re-exports the whole module it is declared in. Skipping it handed Soufflé @@ -91,9 +91,9 @@ _CONFIG = {'config', 'config_key_known', 'config_site'} # silently — the one failure mode this table can have. _KEY_LAYER = {'literal', 'dec_literal'} KIND_SKIPS = { # relations a query of this kind can never reach - 'method': (_REF_LAYER - {'reexport'} - _KEY_LAYER) | _CONFIG, - 'param': (_REF_LAYER - {'reexport'} - _KEY_LAYER) | _CONFIG, - 'var': (_REF_LAYER - {'reexport'} - _KEY_LAYER) | _CONFIG, + 'method': (_REF_LAYER - {'reexport', 'reexport_from'} - _KEY_LAYER) | _CONFIG, + 'param': (_REF_LAYER - {'reexport', 'reexport_from'} - _KEY_LAYER) | _CONFIG, + 'var': (_REF_LAYER - {'reexport', 'reexport_from'} - _KEY_LAYER) | _CONFIG, 'decoration': (_REF_LAYER - _KEY_LAYER) | _CONFIG, 'config': _REF_LAYER - _KEY_LAYER, 'newconst': (_REF_LAYER - {'switch_over'} - _KEY_LAYER) | _CONFIG, @@ -675,7 +675,7 @@ class Impact: return [ln for ln in range(s['line'], min(s['end_line'], len(L)) + 1) if pat.search(L[ln - 1])] # ── facts: the graph, exported once (reused while graph.sqlite is unchanged) ──────────────────────────────── - IMPACT_VERSION = '27' # 27: framework, the engine's framework_edge (#1509); 24: the test* naming convention requires a test class as owner (#1181); 23: owner/member disambiguated by file, two classes of one name no longer merging (#1188); 22: lex_parent, the innermost declaration enclosing each one by span (#1183); 21: cert_tier, the tier -> certainty table the call rules join on (#1131); 20: faccess, the engine's resolved field accesses (#1071); 3: decl_file facts (the import-time test-file rule); 14: the registration-key + IMPACT_VERSION = '28' # 28: reexport_from, the file an `export *` line re-exports; 27: framework, the engine's framework_edge (#1509); 24: the test* naming convention requires a test class as owner (#1181); 23: owner/member disambiguated by file, two classes of one name no longer merging (#1188); 22: lex_parent, the innermost declaration enclosing each one by span (#1183); 21: cert_tier, the tier -> certainty table the call rules join on (#1131); 20: faccess, the engine's resolved field accesses (#1071); 3: decl_file facts (the import-time test-file rule); 14: the registration-key # layer; 15: the registration facts (two 14s landed independently, which is exactly the collision this # guards); 16: regsite folded into ax_registration's reg_key_fact; 20: implements_pair (#1011); 17/18: the tagged-template test registrar # (it.each`…`) and its table span @@ -965,18 +965,12 @@ class Impact: sites.append((ckey(k), c, d, f, l)) W('config_site', sorted(set(sites))) # a barrel: `export { alpha } from './core.js'`. The parser records no reference for that line, so the file - # that has to change in the same commit as a rename was invisible. Read from the source, per module - rex = [] - for f, mid in sorted(mod_of.items()): - if not re.search(r'\.(ts|tsx|js|jsx|mjs|cjs)$', f): continue - for i, line in enumerate(self.code(f), 1): - m = re.match(r'\s*export\s*(type\s*)?\{([^}]*)\}\s*from\s', line) - if m: - for part in m.group(2).split(','): - w = re.findall(r'[A-Za-z_$][\w$]*', part) - if w: rex.append((mid, w[0], f, i)) - elif re.match(r'\s*export\s*\*\s*from\s', line): rex.append((mid, '*', f, i)) - W('reexport', sorted(set(rex))) + # that has to change in the same commit as a rename was invisible. Read from the source, per module, by the + # scanner the SQL port shares; `reexport_from` is the file each `export *` line names, when it is one here + import graph_sql + rex, star = graph_sql.barrel_lines(mod_of, self.code, self.lines, {s['file'] for s in g.sym.values() if s.get('file')}) + W('reexport', rex) + W('reexport_from', star) decs = collections.defaultdict(set) for r in (g.q("SELECT owner_id, name FROM decorations") if g.has('decorations') else []): decs[r[0]].add(r[1].split('.')[-1]) def named_test(i, s): diff --git a/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl b/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl index 573c57a0c..d7f708168 100644 --- a/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl +++ b/plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl @@ -67,6 +67,7 @@ // decorated_name(m, w) the name m denotes what its decorator returned: calling m runs w .decl decorated_name(m:symbol, w:symbol) .input decorated_name .decl reexport(c:symbol, n:symbol, f:symbol, l:number) .input reexport +.decl reexport_from(f:symbol, l:number, src:symbol) .input reexport_from .decl field(fl:symbol, t:symbol, n:symbol, f:symbol, l:number) .input field .decl accessor(fl:symbol, n:symbol, rw:symbol) .input accessor .decl faccess(c:symbol, fl:symbol, acc:symbol, tier:symbol, f:symbol, l:number) .input faccess @@ -604,7 +605,12 @@ direct_edge(q, c, m) :- target(q, "typeparam", m, _), calls(c, m, _, _, _). direct(q, c, "uses", "re-exports it (a barrel: this line must change with a rename)", "text", f, l) :- target(q, "method", m, _), named(n, m), reexport(c, n, f, l). direct(q, c, "uses", "re-exports it (a barrel: this line must change with a rename)", "text", f, l) :- target(q, "field", fl, _), field(fl, _, n, _, _), reexport(c, n, f, l). direct(q, c, "uses", "re-exports it (a barrel: this line must change with a rename)", "text", f, l) :- target(q, "type", t, _), typ(t, n, _), reexport(c, n, f, l). -direct(q, c, "uses", "re-exports the whole module it is declared in (export * — a rename changes what this file exports)", "text", f, l) :- target(q, "method", m, _), decl_file(m, df), reexport(c, "*", f, l), f != df. +// `export *` only from the module it is declared in: the line's own source file, or a file a further `export *` of +// that one reaches. Every `export *` in the repository used to qualify, whatever it pointed at. +.decl star_reach(f:symbol, l:number, df:symbol) +star_reach(f, l, s) :- reexport_from(f, l, s). +star_reach(f, l, d) :- star_reach(f, l, s), reexport_from(s, _, d). +direct(q, c, "uses", "re-exports the whole module it is declared in (export * — a rename changes what this file exports)", "text", f, l) :- target(q, "method", m, _), decl_file(m, df), reexport(c, "*", f, l), star_reach(f, l, df), f != df. // ── a STRING target: a topic, a queue, a route, a bean qualifier — a name in a namespace that is not the code's ── direct(q, c, "uses", "names it in a string literal", "text", f, l) :- target(q, "string", _, v), literal(c, v, f, l). diff --git a/plugins/axiomcode/skills/axiomcode/scripts/graph_sql.py b/plugins/axiomcode/skills/axiomcode/scripts/graph_sql.py index 38b534a16..cca5d6058 100644 --- a/plugins/axiomcode/skills/axiomcode/scripts/graph_sql.py +++ b/plugins/axiomcode/skills/axiomcode/scripts/graph_sql.py @@ -801,7 +801,8 @@ def direct_for_method(q, ids, code=None, rel=None, at=None, lines=None): rows += _bean_definition_consumers(q, ids) # a barrel that re-exports it, or the whole module it is declared in (rules 396 and 399) rows += reexport_rows(q, names, code, rel or (lambda x: x), - {f for (f,) in q(f"SELECT file FROM symbols WHERE id IN ({ph}) AND file IS NOT NULL", *ids)}) + {f for (f,) in q(f"SELECT file FROM symbols WHERE id IN ({ph}) AND file IS NOT NULL", *ids)}, + lines) # alongside: siblings of the target's own type, then the other types declared in the same file owners = {r[0] for r in q(f"SELECT owner FROM symbols WHERE id IN ({ph})", *ids) if r[0]} memb, owner_disp, tfile, _tid, _by_tid = _members(q) @@ -2199,17 +2200,121 @@ def _alongside_for_type(q, tids, inside, target_fields=(), at=None): _REEXPORTS = {} REEXPORT_NAMED = re.compile(r'\s*export\s*(type\s*)?\{([^}]*)\}\s*from\s') REEXPORT_STAR = re.compile(r'\s*export\s*\*\s*from\s') +REEXPORT_STAR_SPEC = re.compile(r'\s*export\s*\*\s*from\s*([\'"])([^\'"]+)\1') MODULE_EXT = re.compile(r'\.(ts|tsx|js|jsx|mjs|cjs)$') +SPEC_EXT = ('', '.ts', '.tsx', '.d.ts', '.js', '.jsx', '.mjs', '.cjs', + '/index.ts', '/index.tsx', '/index.d.ts', '/index.js', '/index.jsx', '/index.mjs', '/index.cjs') + + +def _jsonc(text): + """a tsconfig as JSON: comments and trailing commas dropped, outside strings only (`"@/*"` is not a comment).""" + out = []; i = 0; n = len(text) + while i < n: + ch = text[i] + if ch == '"': + j = i + 1 + while j < n and text[j] != '"': j += 2 if text[j] == '\\' else 1 + out.append(text[i:j + 1]); i = j + 1 + elif text.startswith('//', i): + while i < n and text[i] != '\n': i += 1 + elif text.startswith('/*', i): + e = text.find('*/', i + 2); i = n if e < 0 else e + 2 + else: + out.append(ch); i += 1 + try: return json.loads(re.sub(r',(\s*[}\]])', r'\1', ''.join(out))) + except ValueError: return {} + + +def _ts_paths(f, lines, cache): + """the nearest tsconfig.json above file f -> (its directory, baseUrl, paths); `extends` is not followed.""" + d = os.path.dirname(f) + while True: + if d not in cache: + cfg = os.path.join(d, 'tsconfig.json') if d else 'tsconfig.json' + text = '\n'.join(lines(cfg) or []) + cache[d] = (_jsonc(text).get('compilerOptions') or {}) if text.strip() else None + opts = cache[d] + if opts is not None: + return d, opts.get('baseUrl'), opts.get('paths') or {} + if not d: return '', None, {} + d = os.path.dirname(d) + + +def resolve_module(f, spec, files, lines, cache): + """the repository file a module specifier written in f names, or None (a package, or nothing indexed). + Relative first; otherwise the nearest tsconfig's `paths` and `baseUrl`. A TypeScript ESM import spells the + compiled name (`./x.js` for x.ts), so that extension is also tried off.""" + j = lambda *p: os.path.normpath(os.path.join(*p)).replace(os.sep, '/') + cands = [] + if spec.startswith('.'): + cands.append(j(os.path.dirname(f) or '.', spec)) + elif lines is not None: + cdir, base, paths = _ts_paths(f, lines, cache) + root = j(cdir or '.', base) if base else (cdir or '.') + for pat, subs in paths.items(): + if not isinstance(subs, list): continue + if '*' in pat: + pre, _, post = pat.partition('*') + if not (spec.startswith(pre) and spec.endswith(post) and len(spec) >= len(pre) + len(post)): continue + star = spec[len(pre):len(spec) - len(post)] + elif spec != pat: continue + else: star = '' + cands += [j(root, s.replace('*', star)) for s in subs if isinstance(s, str)] + if base: cands.append(j(root, spec)) + for c in cands: + stems = [c] + ([c.rsplit('.', 1)[0]] if re.search(r'\.(js|jsx|mjs|cjs)$', c) else []) + for s in stems: + for e in SPEC_EXT: + if s + e in files: return s + e + return None + + +def barrel_lines(mod_of, code, lines=None, files=()): + """the barrel lines of every module -> (reexport rows (c,n,f,l), export-* sources (f,l,src_file)). + Shared by the exporter and the SQL port so the two read a barrel the same way. The specifier is read off + the raw line (`code` blanks string literals); a `export *` whose source is not a file of this repository + (a package, an alias no tsconfig maps) has no source row.""" + rex = []; star = []; files = set(files); cache = {} + for f, mid in sorted(mod_of.items()): + if not MODULE_EXT.search(f): continue + raw = lines(f) if lines is not None else [] + for i, line in enumerate(code(f), 1): + m = REEXPORT_NAMED.match(line) + if m: + for part in m.group(2).split(','): + w = re.findall(r'[A-Za-z_$][\w$]*', part) + if w: rex.append((mid, w[0], f, i)) + elif REEXPORT_STAR.match(line): + rex.append((mid, '*', f, i)) + s = REEXPORT_STAR_SPEC.match(raw[i - 1]) if i <= len(raw) else None + src = s and resolve_module(f, s.group(2), files, lines, cache) + if src: star.append((f, i, src)) + return sorted(set(rex)), sorted(set(star)) + + +def star_reach(star): + """star_reach(f,l,df) — the files an `export *` at f:l re-exports, through every further `export *` on the way.""" + nxt = collections.defaultdict(set) + for f, _l, s in star: nxt[f].add(s) + out = {} + for f, l, s in star: + seen = {s}; todo = [s] + while todo: + for d in nxt.get(todo.pop(), ()): + if d not in seen: seen.add(d); todo.append(d) + out[(f, l)] = seen + return out -def reexports(q, code): - """`reexport(c,n,f,l)` — a barrel: `export { alpha } from './core.js'`, and `export * from …` as the name `*`. +def reexports(q, code, lines=None): + """`reexport(c,n,f,l)` — a barrel: `export { alpha } from './core.js'`, and `export * from …` as the name `*`; + with it `star_reach`, the modules each `export *` line re-exports (keyed (f,l)). The parser records no reference for that line, so the file that has to change in the same commit as a rename was invisible to every other relation. Read from the source per module, exactly as the exporter reads it — this was the second reason a bundle with any JS or TS module in it declined here. """ - key = id(q) + key = (id(q), lines is not None) if key in _REEXPORTS: return _REEXPORTS[key] # `mod_of` the way the exporter builds it, which is not "every module row". It walks `g.sym`, and that is # {r['id']: dict(r) for r in SELECT * WHERE method_id IS NOT NULL OR type_id IS NOT NULL} @@ -2223,37 +2328,29 @@ def reexports(q, code): mod_of = {} for i, (f, kind) in one.items(): if kind == 'module' and f: mod_of.setdefault(f, i) - rex = [] - for f, mid in sorted(mod_of.items()): - if not MODULE_EXT.search(f): continue - for i, line in enumerate(code(f), 1): - m = REEXPORT_NAMED.match(line) - if m: - for part in m.group(2).split(','): - w = re.findall(r'[A-Za-z_$][\w$]*', part) - if w: rex.append((mid, w[0], f, i)) - elif REEXPORT_STAR.match(line): - rex.append((mid, '*', f, i)) - return _REEXPORTS.setdefault(key, sorted(set(rex))) + rex, star = barrel_lines(mod_of, code, lines, {f for f, _k in one.values() if f}) + return _REEXPORTS.setdefault(key, (rex, star_reach(star))) -def reexport_rows(q, names, code, rel, decl_files=()): +def reexport_rows(q, names, code, rel, decl_files=(), lines=None): """Rules 396-399 — the barrel lines that must change with a rename of the target. `decl_files` is given for a METHOD target only: rule 399 says that re-exporting the whole module a method is - declared in is a dependency too, but only from a DIFFERENT file than the one that declares it. + declared in is a dependency too, but only from a DIFFERENT file than the one that declares it, and only when + that `export *` reaches the declaring file (star_reach) — not every `export *` in the repository. """ if code is None: return [] rows = [] - for c, n, f, l in reexports(q, code): + rex, reach = reexports(q, code, lines) + for c, n, f, l in rex: if n in names: rows.append((c, 'uses', 're-exports it (a barrel: this line must change with a rename)', 'text', rel(f) if f else '', l or 0)) - # `… decl_file(m,df), reexport(c,"*",f,l), f != df` — m ranges over EVERY declaration the query resolved - # to, so the row exists when SOME target is declared outside this file, not when all of them are. A target - # that resolves to many declarations (an anonymous callable) has one in the barrel's own file often - # enough for the two readings to differ. - elif n == '*' and (decl_files - {f}): + # `… decl_file(m,df), reexport(c,"*",f,l), star_reach(f,l,df), f != df` — m ranges over EVERY declaration + # the query resolved to, so the row exists when SOME target is declared in a file this line re-exports, not + # when all of them are. A target that resolves to many declarations (an anonymous callable) has one in the + # barrel's own file often enough for the two readings to differ. + elif n == '*' and ((set(decl_files) - {f}) & reach.get((f, l), set())): rows.append((c, 'uses', 're-exports the whole module it is declared in (export * — a rename changes ' 'what this file exports)', 'text', rel(f) if f else '', l or 0)) return rows diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/case.json b/tests/cases/typescript/export-star-scoped-to-its-source/case.json new file mode 100644 index 000000000..8682eafbe --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/case.json @@ -0,0 +1,6 @@ +{"lang": "typescript", "src": "src", + "checks": [ + {"why": "an export * is a dependent of a method only when it re-exports the module the method is declared in, directly or through a further export *", "run": ["impact", "slugify"], "want": ["src/util/index.ts:1 — re-exports", "src/index.ts:1 — re-exports", "re-exports the whole module"], "avoid": ["src/util/index.ts:2 — re-exports", "src/index.ts:2 — re-exports", "src/aliased.ts:1 — re-exports", "src/testing.ts:1 — re-exports", "(2 sites)"]}, + {"why": "a method no export * reaches gets none of them: every export * in the repository used to be listed", "run": ["impact", "score"], "want": ["src/index.ts:2 — re-exports", "re-exports the whole module"], "avoid": ["src/index.ts:1 — re-exports", "src/util/index.ts:1 — re-exports", "src/util/index.ts:2 — re-exports", "src/aliased.ts:1 — re-exports", "src/testing.ts:1 — re-exports", "(2 sites)"]}, + {"why": "a TypeScript ESM specifier names the compiled file (./extra.js for extra.ts), and a tsconfig paths alias names a repository file", "run": ["impact", "trim", "clamp"], "want": ["src/util/index.ts:2 — re-exports", "src/index.ts:1 — re-exports", "src/aliased.ts:1 — re-exports"], "avoid": ["src/util/index.ts:1 — re-exports", "src/index.ts:2 — re-exports", "src/testing.ts:1 — re-exports", "(2 sites)"]}, + {"why": "control: the barrel's own declaration is not re-exported by the barrel's export * lines, but is by the export * that points at the barrel", "run": ["impact", "helper"], "want": ["src/index.ts:1 — re-exports", "re-exports the whole module"], "avoid": ["src/util/index.ts:1 — re-exports", "src/util/index.ts:2 — re-exports", "src/index.ts:2 — re-exports", "src/aliased.ts:1 — re-exports", "src/testing.ts:1 — re-exports", "(2 sites)"]}]} diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/aliased.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/aliased.ts new file mode 100644 index 000000000..1888f4b19 --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/aliased.ts @@ -0,0 +1 @@ +export * from '@util/numbers'; diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/app.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/app.ts new file mode 100644 index 000000000..f16b8f378 --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/app.ts @@ -0,0 +1,4 @@ +import { slugify, trim, helper, score } from './index'; +import { clamp as bound } from './aliased'; + +export function run(): string { return slugify('A') + trim(' b') + helper() + score(1) + bound(2); } diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/feature.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/feature.ts new file mode 100644 index 000000000..ca7e089a2 --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/feature.ts @@ -0,0 +1,3 @@ +import { clamp } from './util/numbers'; + +export function score(n: number): number { return clamp(n); } diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/index.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/index.ts new file mode 100644 index 000000000..42c14f7d8 --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/index.ts @@ -0,0 +1,2 @@ +export * from './util'; +export * from './feature'; diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/testing.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/testing.ts new file mode 100644 index 000000000..f7cc5f51d --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/testing.ts @@ -0,0 +1 @@ +export * from 'some-package'; diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/util/extra.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/extra.ts new file mode 100644 index 000000000..36c983145 --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/extra.ts @@ -0,0 +1 @@ +export function trim(s: string): string { return s.trim(); } diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/util/index.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/index.ts new file mode 100644 index 000000000..44310ebdc --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/index.ts @@ -0,0 +1,3 @@ +export * from './strings'; +export * from './extra.js'; +export function helper(): number { return 1; } diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/util/numbers.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/numbers.ts new file mode 100644 index 000000000..72c06111e --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/numbers.ts @@ -0,0 +1 @@ +export function clamp(n: number): number { return Math.max(0, n); } diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/src/util/strings.ts b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/strings.ts new file mode 100644 index 000000000..13fd1296d --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/src/util/strings.ts @@ -0,0 +1 @@ +export function slugify(s: string): string { return s.toLowerCase(); } diff --git a/tests/cases/typescript/export-star-scoped-to-its-source/tsconfig.json b/tests/cases/typescript/export-star-scoped-to-its-source/tsconfig.json new file mode 100644 index 000000000..9a3e4b33b --- /dev/null +++ b/tests/cases/typescript/export-star-scoped-to-its-source/tsconfig.json @@ -0,0 +1,7 @@ +{ + // the alias the aliased barrel is written with; "@util/*" is not a comment + "compilerOptions": { + "baseUrl": ".", + "paths": { "@util/*": ["src/util/*"], }, + }, +}