Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format of this changelog is based on [Keep a Changelog](https://keepachangel
- `.kts` now reports as Kotlin and `.gradle` as Groovy. Both were missing from the extension table, so Kotlin scripts and every Gradle build script — `build.gradle`, `settings.gradle`, `build.gradle.kts`, `settings.gradle.kts` — reported `Unknown`. Gradle selects the DSL by extension alone, so the mapping is exact. Repositories built with Gradle will now show a small Groovy or Kotlin count from their build scripts.
- `build.gradle.kts` now carries the same `gradle`, `build` and `dependencies` tags as `build.gradle`. A project using the Gradle Kotlin DSL previously looked like it declared no dependencies.
- `settings.gradle` and `settings.gradle.kts` are tagged `gradle` and `build`. They declare project structure rather than dependencies, so they do not carry `dependencies`.
- `.cc`, `.cxx` and `.c++` now report as `C++`, and `.hpp`, `.hh`, `.hxx` and `.h++` as `C++ Header`, a new language paralleling `C Header`. Only `.cpp` was mapped before, so the rest reported `Unknown`. `.h` is unchanged and still reports `C Header`.
- `.mm` now reports as `Objective-C++`, a new language. It was missing from the extension table and reported `Unknown`.
- Swift Package Manager files are recognised. `Package.swift` is tagged `build`, `dependencies`, `Swift` and `SwiftPM`; its lock file `Package.resolved` is tagged `dependencies`, `Swift` and `SwiftPM` and reports as `JSON` instead of `Unknown`. A Swift package previously looked like it declared no dependencies. `Swift` and `SwiftPM` are new tags.
- CocoaPods files are recognised. `Podfile` and `Podfile.lock` are tagged `dependencies` and `CocoaPods`, and `Podfile` reports as `Ruby` instead of `Unknown`.
Expand All @@ -16,7 +17,7 @@ The format of this changelog is based on [Keep a Changelog](https://keepachangel
All of these are new tags on previously untagged files, so kospex is compatible either way; a re-sync is needed to pick them up.

### Changed
- The tag vocabulary grew from 119 to 123 tags, the filetype vocabulary from 86 to 88 and the language vocabulary from 32 to 33.
- The tag vocabulary grew from 119 to 123 tags, the filetype vocabulary from 86 to 89 and the language vocabulary from 32 to 34.
- The Click floor moved from `>=8.3.1` to `>=8.3.3`, so an install can no longer resolve to a vulnerable Click release.
- The package author email is now `peter@kospex.io`.

Expand Down
75 changes: 75 additions & 0 deletions changes/2026-10-cpp-extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# C++ Source and Header Extensions

Closes [#43](https://github.com/kospex/panopticas/issues/43).

## Summary

`.cpp` was the only C++ extension in `EXT_FILETYPES`. Every other common
spelling reported `Unknown`:

file.cc 'Unknown' file.hpp 'Unknown'
file.cxx 'Unknown' file.hh 'Unknown'
file.c++ 'Unknown' file.hxx 'Unknown'
file.h++ 'Unknown'

A C++ codebase using `.cc` and `.hpp` — a very common pairing — showed no C++
at all. The gap surfaced during the `.m`/`.h` analysis in #40.

## Changes

Seven entries in `EXT_FILETYPES`, and `C++ Header` added to
`LANGUAGE_FILETYPES`:

".cc": "C++" ".hpp": "C++ Header"
".cxx": "C++" ".hh": "C++ Header"
".c++": "C++" ".hxx": "C++ Header"
".h++": "C++ Header"

## Source

The [GCC documentation](https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html)
lists the suffixes it treats as C++:

- **Source:** `.cc`, `.cp`, `.cxx`, `.cpp`, `.CPP`, `.c++`, `.C`
- **Header:** `.hh`, `.H`, `.hp`, `.hxx`, `.hpp`, `.HPP`, `.h++`, `.tcc`

## Design notes

- **`C++ Header` is a new language name**, paralleling the existing
`C Header`. Headers are reported separately from sources because that is
what panopticas already does for C. Reporting them as plain `C++` was the
alternative; it would have been simpler but inconsistent with `.h`.
- **`.h` is unchanged.** It is shared by C, C++ and Objective-C — GCC's own
entry for it reads "C, C++, Objective-C or Objective-C++ header file" — and
cannot be resolved from the path. That is the subject of #40.
- Additive only: no existing mapping changes, so kospex is unaffected until a
re-sync.

## Not mapped

The complete list of C++-related suffixes considered and left out, so none
needs rediscovering.

| Suffix | What it is | Reports as | Why not mapped |
|---|---|---|---|
| `.C` | C++ source (GCC, literal capital) | `C` | panopticas lowercases the extension before lookup, so it is indistinguishable from `.c` |
| `.H` | C++ header (GCC, literal capital) | `C Header` | Same: indistinguishable from `.h` |
| `.cp` | C++ source (GCC) | `Unknown` | Rare, and short enough to collide with unrelated uses |
| `.hp` | C++ header (GCC) | `Unknown` | Rare |
| `.tcc` | C++ header holding template definitions (GCC) | `Unknown` | Rare |
| `.ii` | C++ source that should not be preprocessed (GCC) | `Unknown` | Compiler output, not something a repository normally holds |
| `.ipp`, `.tpp`, `.inl`, `.txx` | Template and inline implementation files | `Unknown` | Project conventions rather than compiler-defined suffixes; not checked against any documentation |
| `.cppm`, `.ixx`, `.ccm`, `.mpp` | C++20 module interface units | `Unknown` | Vary by compiler; not checked against any compiler's documentation |
| `.h` | C, C++ or Objective-C header | `C Header` | Shared by three languages and not resolvable from the path — see #40 |

`.CPP` and `.HPP`, which GCC lists separately, need no entry: lowercasing
resolves them to `.cpp` and `.hpp`, so they report `C++` and `C++ Header`.

The first six rows are from the GCC suffix list above. The template and module
rows are from general knowledge and would need verifying before any of them
became a mapping.

## Tests

`tests/test_panopticas.py` covers all seven extensions, asserts `.h` still
reports `C Header`, and has a fixture for each extension in `src/tests/`.
6 changes: 3 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ EXT_FILETYPES["dockerfile"] # "Dockerfile" (special case)
`EXT_FILETYPES` mixes two kinds of value: things that are programming languages
(`Python`, `Go`) and things that are file types but not languages (`PNG`,
`Gitignore`, `Lock`). These two collections classify every value in the table —
33 languages and 55 non-languages. `get_languages()` and `get_filetypes()` read
34 languages and 55 non-languages. `get_languages()` and `get_filetypes()` read
from them.

```python
Expand Down Expand Up @@ -386,7 +386,7 @@ Return every file type panopticas recognises, languages or not.
from panopticas.core import get_filetypes

get_filetypes()
# Returns: ['Apache JMeter', 'ASP.NET', ..., 'ZIP'] (88 file types)
# Returns: ['Apache JMeter', 'ASP.NET', ..., 'ZIP'] (89 file types)
```

**Returns:** Sorted list of file type strings
Expand All @@ -399,7 +399,7 @@ Return every file type that is a programming language.
from panopticas.core import get_languages

get_languages()
# Returns: ['C', 'C Header', 'C#', 'C++', 'CSS', ..., 'Vue'] (33 languages)
# Returns: ['C', 'C Header', 'C#', 'C++', 'C++ Header', ..., 'Vue'] (34 languages)
```

**Returns:** Sorted list of language names — the subset of `get_filetypes()`
Expand Down
4 changes: 2 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ count.
| Command | Shape |
|---|---|
| `tags` | `{"tags": [...], "count": 123}` |
| `languages` | `{"languages": [...], "count": 33}` |
| `filetypes` | `{"filetypes": [...], "count": 88}` |
| `languages` | `{"languages": [...], "count": 34}` |
| `filetypes` | `{"filetypes": [...], "count": 89}` |

```console
$ panopticas languages --json | jq -r '.count'
Expand Down
8 changes: 8 additions & 0 deletions src/panopticas/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@

EXT_FILETYPES = {
".c": "C",
".c++": "C++",
".cc": "C++",
".class": "Java Class",
".cpp": "C++",
".cxx": "C++",
".cs": "C#",
".cjs": "JavaScript", # CommonJS module, explicit
".cts": "TypeScript", # TypeScript emitting CommonJS
Expand All @@ -42,6 +45,10 @@
".groovy": "Groovy",
".gsp": "Groovy Server Pages",
".h": "C Header",
".h++": "C++ Header",
".hh": "C++ Header",
".hpp": "C++ Header",
".hxx": "C++ Header",
".aspx": "ASP.NET",
".ascx": "ASP.NET User Control",
".htm": "HTML",
Expand Down Expand Up @@ -179,6 +186,7 @@
"C Header",
"C#",
"C++",
"C++ Header",
"CSS",
"Dockerfile",
"Go",
Expand Down
6 changes: 6 additions & 0 deletions src/tests/file.c++
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <iostream>

int main() {
std::cout << "hello" << std::endl;
return 0;
}
6 changes: 6 additions & 0 deletions src/tests/file.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <iostream>

int main() {
std::cout << "hello" << std::endl;
return 0;
}
6 changes: 6 additions & 0 deletions src/tests/file.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <iostream>

int main() {
std::cout << "hello" << std::endl;
return 0;
}
10 changes: 10 additions & 0 deletions src/tests/file.h++
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

namespace example {

class Greeter {
public:
void greet() const;
};

} // namespace example
10 changes: 10 additions & 0 deletions src/tests/file.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

namespace example {

class Greeter {
public:
void greet() const;
};

} // namespace example
10 changes: 10 additions & 0 deletions src/tests/file.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

namespace example {

class Greeter {
public:
void greet() const;
};

} // namespace example
10 changes: 10 additions & 0 deletions src/tests/file.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

namespace example {

class Greeter {
public:
void greet() const;
};

} // namespace example
24 changes: 24 additions & 0 deletions tests/test_panopticas.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ def test_kotlin(self):
def test_kotlin_script(self):
assert get_extension_filetype(".kts") == "Kotlin"

@pytest.mark.parametrize("ext", [".cc", ".cxx", ".c++"])
def test_cpp_source_extensions(self, ext):
assert get_extension_filetype(ext) == "C++"

@pytest.mark.parametrize("ext", [".hpp", ".hh", ".hxx", ".h++"])
def test_cpp_header_extensions(self, ext):
assert get_extension_filetype(ext) == "C++ Header"

def test_plain_h_is_still_a_c_header(self):
# .h is shared by C, C++ and Objective-C and is not resolved here.
assert get_extension_filetype(".h") == "C Header"

def test_podfile_is_ruby(self):
# CocoaPods loads an extensionless Podfile as Ruby.
assert get_extension_filetype("Podfile") == "Ruby"
Expand Down Expand Up @@ -776,6 +788,18 @@ def test_gradle_kotlin_settings_detected(self, fixtures_exist):
def test_objective_cpp_detected(self, fixtures_exist):
assert get_language("file.mm") == "Objective-C++"

@pytest.mark.parametrize("filename", [
"file.cc", "file.cxx", "file.c++",
])
def test_cpp_sources_detected(self, fixtures_exist, filename):
assert get_language(filename) == "C++"

@pytest.mark.parametrize("filename", [
"file.hpp", "file.hh", "file.hxx", "file.h++",
])
def test_cpp_headers_detected(self, fixtures_exist, filename):
assert get_language(filename) == "C++ Header"

def test_podfile_detected(self, fixtures_exist):
assert get_language("Podfile") == "Ruby"

Expand Down
Loading